BDSMLR Download helper

Download links, download all, size filters and auto scrolling

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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

})();