BDSMLR Download helper

Download links, download all, size filters and auto scrolling

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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 });

})();