BDSMLR Download helper

Download links, download all, size filters and auto scrolling

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         BDSMLR Download helper
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Download links, download all, size filters and auto scrolling
// @match        *://*.bdsmlr.com/*
// @grant        GM_download
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const panel = document.createElement('div');
    panel.id = 'image-download-panel';
    panel.textContent = '📥 Image Download Panel';
    panel.style.position = 'fixed';
    panel.style.bottom = '20px';
    panel.style.right = '20px';
    panel.style.width = '320px';
    panel.style.maxHeight = '50vh';
    panel.style.overflowY = 'auto';
    panel.style.overflowX = 'hidden';
    panel.style.padding = '12px';
    panel.style.backgroundColor = 'white';
    panel.style.border = '1px solid #ccc';
    panel.style.borderRadius = '6px';
    panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
    panel.style.zIndex = '9999';
    panel.style.fontFamily = 'sans-serif';
    panel.style.fontSize = '14px';

    document.body.appendChild(panel);

    // Container for controls and image list
    const controls = document.createElement('div');
    controls.style.marginBottom = '10px';
    controls.style.flexShrink = '0';
    panel.appendChild(controls);

    const listContainer = document.createElement('div');
    listContainer.style.flexGrow = '1';
    listContainer.style.overflowY = 'auto';
    panel.appendChild(listContainer);

    // Helper: Create labeled slider input
    function createSlider(labelText, maxValue, defaultValue) {
        const container = document.createElement('div');
        container.style.marginBottom = '8px';

        const label = document.createElement('label');
        label.style.display = 'block';
        label.style.marginBottom = '2px';
        label.textContent = labelText;

        const valueSpan = document.createElement('span');
        valueSpan.style.marginLeft = '8px';
        valueSpan.textContent = defaultValue;

        const input = document.createElement('input');
        input.type = 'range';
        input.min = '0';
        input.max = maxValue.toString();
        input.value = defaultValue.toString();
        input.style.width = '90%';

        input.addEventListener('input', () => {
            valueSpan.textContent = input.value;
            filterAndRenderImages();
        });

        container.appendChild(label);
        container.appendChild(input);
        container.appendChild(valueSpan);

        return { container, input };
    }

    // Create sliders
    const widthSlider = createSlider('Min Width (px):', 1000, 0);
    const heightSlider = createSlider('Min Height (px):', 1000, 0);

    controls.appendChild(widthSlider.container);
    controls.appendChild(heightSlider.container);

    // Create "Download All" button
    const downloadAllBtn = document.createElement('button');
    downloadAllBtn.textContent = '⬇️ Download All';
    downloadAllBtn.style.marginBottom = '10px';
    downloadAllBtn.style.padding = '6px 12px';
    downloadAllBtn.style.fontSize = '14px';
    downloadAllBtn.style.cursor = 'pointer';
    downloadAllBtn.style.backgroundColor = '#3498db';
    downloadAllBtn.style.color = 'white';
    downloadAllBtn.style.border = 'none';
    downloadAllBtn.style.borderRadius = '4px';
    downloadAllBtn.style.userSelect = 'none';

    downloadAllBtn.addEventListener('click', () => {
        const minWidth = parseInt(widthSlider.input.value, 10);
        const minHeight = parseInt(heightSlider.input.value, 10);

        const filtered = imagesData.filter(data => data.width >= minWidth && data.height >= minHeight);

        if (filtered.length === 0) {
            alert('No images match the filter criteria to download.');
            return;
        }

        progress.max = filtered.length;
        progress.value = 0;

        filtered.forEach((data, idx) => {
            GM_download({
                url: data.url,
                name: data.filename,
                headers: { 'Referer': location.origin },
                onload: () => {
                    progress.value += 1;
                },
                onerror: err => {
                    console.error(`Download failed for ${data.filename}:`, err);
                    progress.value += 1; // still advance to avoid hanging the bar
                }
            });
        });
    });


    controls.insertBefore(downloadAllBtn, controls.firstChild);

    // Create the progress bar
    const progress = document.createElement('progress');
    progress.value = 0;
    progress.max = 100;
    progress.style.width = '100%';
    progress.style.marginTop = '10px';
    progress.style.height = '20px';

    // Add it to your controls area in the panel
    controls.appendChild(progress);

    let autoScrollEnabled = false;
    let autoScrollInterval = null;

    // Create the button
    const autoScrollBtn = document.createElement('button');
    autoScrollBtn.textContent = '▶️ Auto-Scroll';
    autoScrollBtn.style.margin = '5px';
    autoScrollBtn.style.padding = '4px 8px';
    autoScrollBtn.style.fontSize = '13px';
    autoScrollBtn.style.cursor = 'pointer';

    // Add to your panel's controls area
    controls.appendChild(autoScrollBtn);

    // Function to toggle auto-scrolling
    autoScrollBtn.addEventListener('click', () => {
        autoScrollEnabled = !autoScrollEnabled;

        if (autoScrollEnabled) {
            autoScrollBtn.textContent = '⏹️ Stop Auto-Scroll';
            autoScrollInterval = setInterval(() => {
                window.scrollBy(0, 500);  // scroll down 500px
            }, 500);  // every 500ms
        } else {
            autoScrollBtn.textContent = '▶️ Auto-Scroll';
            clearInterval(autoScrollInterval);
        }
    });

    // Store images info: { img, url, width, height, filename, containerDiv }
    const imagesData = [];

    // Utility to get filename from URL
    function getFilenameFromUrl(url, index) {
        try {
            const urlObj = new URL(url);
            const pathname = urlObj.pathname;
            const filename = pathname.substring(pathname.lastIndexOf('/') + 1).split('?')[0];
            return filename || `image_${index + 1}.jpg`;
        } catch (e) {
            return `image_${index + 1}.jpg`;
        }
    }

    function createImageLink(imageInfo, index) {
        const container = document.createElement('div');
        container.style.marginBottom = '8px';
        container.style.display = 'flex';
        container.style.alignItems = 'center';
        container.style.gap = '8px';  // spacing between thumbnail and link

        // Thumbnail image
        const thumb = document.createElement('img');
        thumb.src = imageInfo.url;
        thumb.alt = `Image ${index + 1}`;
        thumb.style.width = '40px';
        thumb.style.height = '40px';
        thumb.style.objectFit = 'cover';
        thumb.style.border = '1px solid #ccc';
        thumb.style.borderRadius = '3px';
        thumb.style.flexShrink = '0';

        // Download link
        const link = document.createElement('a');
        link.href = '#';
        link.textContent = `📥 Image ${index + 1} (${imageInfo.width}×${imageInfo.height})`;
        link.style.color = '#3498db';
        link.style.textDecoration = 'underline';
        link.style.cursor = 'pointer';
        link.style.userSelect = 'none';

        link.addEventListener('click', e => {
            e.preventDefault();
            GM_download({
                url: imageInfo.url,
                name: imageInfo.filename,
                headers: { 'Referer': location.origin },
                onerror: err => {
                    alert(`Download failed for ${imageInfo.filename}: ${err.error}`);
                    console.error('Download failed:', err);
                }
            });
        });

        container.appendChild(thumb);
        container.appendChild(link);

        return container;
    }


    function addImage(img) {
        const url = img.currentSrc || img.src;
        if (!url) return false;
        if (imagesData.some(data => data.url === url)) return false; // avoid duplicates

        function processImage() {
            const width = img.naturalWidth || img.width || 0;
            const height = img.naturalHeight || img.height || 0;
            const filename = getFilenameFromUrl(url, imagesData.length);

            // Check if image was already added (race condition)
            if (imagesData.some(data => data.url === url)) return;

            const containerDiv = createImageLink({ url, width, height, filename }, imagesData.length);

            imagesData.push({ img, url, width, height, filename, containerDiv });
            filterAndRenderImages();
        }

        if (img.complete && img.naturalWidth && img.naturalHeight) {
            // Image already loaded
            processImage();
        } else {
            // Wait for image to load to get natural size
            img.addEventListener('load', () => {
                processImage();
            }, { once: true });
        }

        return true;
    }


    // Render the filtered image list into the panel
    function filterAndRenderImages() {
        const minWidth = parseInt(widthSlider.input.value, 10);
        const minHeight = parseInt(heightSlider.input.value, 10);

        listContainer.innerHTML = '';

        const filtered = imagesData.filter(data => data.width >= minWidth && data.height >= minHeight);

        if (filtered.length === 0) {
            const noImages = document.createElement('div');
            noImages.textContent = 'No images match the filter criteria.';
            listContainer.appendChild(noImages);
            return;
        }

        filtered.forEach((data, idx) => {
            // Adjust the label to show updated index in filtered list
            const container = data.containerDiv;
            container.querySelector('a').textContent =
                `📥 Image ${idx + 1} (${data.width}×${data.height})`;
            listContainer.appendChild(container);
        });
    }

    // Initial populate images from existing <img> tags
    function populateImages() {
        const imgs = Array.from(document.querySelectorAll('img'));
        let addedCount = 0;
        imgs.forEach(img => {
            if (addImage(img)) addedCount++;
        });
        filterAndRenderImages();
        return addedCount;
    }

    populateImages();

    // Observe new images added dynamically
    const observer = new MutationObserver(mutations => {
        let newAdded = false;
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === 1) { // ELEMENT_NODE
                    if (node.tagName === 'IMG') {
                        if (addImage(node)) newAdded = true;
                    } else if (node.querySelectorAll) {
                        const imgs = node.querySelectorAll('img');
                        imgs.forEach(img => {
                            if (addImage(img)) newAdded = true;
                        });
                    }
                }
            });
        });
        if (newAdded) filterAndRenderImages();
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();