Simfileshare Folder Downloader

Adds a button to download all files in a Simfileshare folder as a zip file and individual download buttons

// ==UserScript==
// @name         Simfileshare Folder Downloader
// @namespace    https://github.com/dear-clouds/mio-userscripts
// @version      1.0
// @description  Adds a button to download all files in a Simfileshare folder as a zip file and individual download buttons
// @author       Mio.
// @supportURL   https://github.com/dear-clouds/mio-userscripts/issues
// @icon         https://www.google.com/s2/favicons?sz=64&domain=simfileshare.net
// @match        *://*.simfileshare.net/folder/*
// @grant        none
// @license      GPL-3.0
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==

(function() {
    'use strict';

    window.addEventListener('load', () => {
        const MAX_CONCURRENT_DOWNLOADS = 8; // Control how many files to download concurrently here

        // console.log('Page loaded, initializing script...');

        // Button Download All
        const downloadAllButton = document.createElement('button');
        downloadAllButton.innerHTML = 'Download All Files as ZIP';
        const loaderSpanAll = document.createElement('span');
        loaderSpanAll.className = 'loader';
        loaderSpanAll.style.display = 'none';
        downloadAllButton.appendChild(loaderSpanAll);
        downloadAllButton.style.padding = '10px';
        downloadAllButton.style.backgroundColor = '#4CAF50';
        downloadAllButton.style.color = 'white';
        downloadAllButton.style.border = 'none';
        downloadAllButton.style.cursor = 'pointer';
        downloadAllButton.style.marginBottom = '10px';
        downloadAllButton.style.position = 'relative';

        const h4Element = document.querySelector('h4');
        if (h4Element) {
            h4Element.parentNode.insertBefore(downloadAllButton, h4Element.nextSibling);
            // console.log('Download All button appended successfully');
        }

        const loaderStyle = document.createElement('style');
        loaderStyle.innerHTML = `
            .loader {
                border: 4px solid #f3f3f3;
                border-top: 4px solid #3498db;
                border-radius: 50%;
                width: 16px;
                height: 16px;
                animation: spin 1s linear infinite;
                position: relative;
                margin-left: 10px;
            }
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
            .dl-button {
                margin-left: 10px;
                padding: 4px 8px;
                background-color: #3498db;
                color: white;
                border: none;
                cursor: pointer;
                font-size: 0.9em;
                position: relative;
            }
        `;
        document.head.appendChild(loaderStyle);
        // console.log('Loader styles added');

        downloadAllButton.addEventListener('click', async () => {
            console.log('Download All button clicked');

            // Get all the download links in the folder
            const downloadLinks = document.querySelectorAll('a[href*="/download/"]');
            console.log(`Found ${downloadLinks.length} download links`);

            if (downloadLinks.length === 0) {
                alert('No files found to download!');
                return;
            }

            loaderSpanAll.style.display = 'inline-block';
            // console.log('Loader displayed for Download All button');

            // Create a ZIP file with JSZip
            const zip = new JSZip();
            const folderTitle = h4Element.textContent.replace('Folder: ', '').trim();
            console.log(`Folder title determined: ${folderTitle}`);

            // Function to fetch files with retries and concurrency limit
            const fetchFileWithRetry = async (url, retries = 20) => { // Here you can control the number of retries. Default: 20
                for (let attempt = 0; attempt < retries; attempt++) {
                    try {
                        const directUrl = url.replace('/download/', '/cdn/download/') + '/?dl';
                        console.log(`Fetching: ${directUrl} (Attempt ${attempt + 1})`);
                        const response = await fetch(directUrl);
                        if (response.ok) {
                            console.log(`Successfully fetched: ${directUrl}`);
                            return await response.blob();
                        } else {
                            console.warn(`Attempt ${attempt + 1} failed: ${response.statusText}`);
                        }
                    } catch (error) {
                        console.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
                    }
                    await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retrying
                }
                throw new Error(`Failed to fetch file after ${retries} attempts: ${url}`);
            };

            // Concurrency limiting function
            const limitConcurrency = async (limit, tasks) => {
                const results = [];
                const executing = [];

                for (const task of tasks) {
                    const p = task().then(result => {
                        executing.splice(executing.indexOf(p), 1);
                        return result;
                    });
                    results.push(p);
                    executing.push(p);

                    if (executing.length >= limit) {
                        await Promise.race(executing);
                    }
                }
                return Promise.all(results);
            };

            // Prepare a list of tasks to fetch each file
            const tasks = Array.from(downloadLinks).map(link => async () => {
                try {
                    console.log(`Preparing to fetch file: ${link.href}`);
                    const blob = await fetchFileWithRetry(link.href);
                    const filename = link.textContent.trim() || `file_${Math.random().toString(36).substring(7)}`;
                    console.log(`Adding file to ZIP: ${filename}`);
                    zip.file(filename, blob);
                } catch (error) {
                    console.error(error.message);
                }
            });

            // Run tasks with controlled concurrency
            await limitConcurrency(MAX_CONCURRENT_DOWNLOADS, tasks);
            // console.log('All files fetched, generating ZIP');

            // Generate ZIP and trigger download
            zip.generateAsync({ type: "blob" }).then(content => {
                saveAs(content, `${folderTitle}.zip`);
                loaderSpanAll.style.display = 'none';
                console.log('ZIP generated and download triggered');
            }).catch(error => {
                console.error('Error generating ZIP:', error);
                loaderSpanAll.style.display = 'none';
                alert('Error occurred while generating ZIP.');
            });
        });

        // Add individual download buttons next to each file link
        const fileLinks = document.querySelectorAll('tr td a[href*="/download/"]');
        fileLinks.forEach(link => {
            const dlButton = document.createElement('button');
            dlButton.innerHTML = 'DL';
            const loaderSpan = document.createElement('span');
            loaderSpan.className = 'loader';
            loaderSpan.style.display = 'none';
            dlButton.appendChild(loaderSpan);
            dlButton.className = 'dl-button';
            dlButton.addEventListener('click', async (e) => {
                e.preventDefault();
                console.log(`Individual download button clicked for link: ${link.href}`);
                loaderSpan.style.display = 'inline-block';
                // console.log('Loader displayed for individual download button');
                try {
                    // First visit the link to prepare the direct download
                    await fetch(link.href, { method: 'GET', mode: 'no-cors' });
                    const directUrl = link.href.replace('/download/', '/cdn/download/') + '/?dl';
                    console.log(`Fetching direct URL: ${directUrl}`);
                    const response = await fetch(directUrl);
                    if (!response.ok) {
                        throw new Error(`Failed to download: ${response.statusText}`);
                    }
                    const blob = await response.blob();
                    const filename = link.textContent.trim();
                    console.log(`Successfully downloaded file: ${filename}`);
                    const a = document.createElement('a');
                    a.href = URL.createObjectURL(blob);
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    console.log(`File download triggered: ${filename}`);
                } catch (error) {
                    console.error('Error downloading file:', error);
                    alert(`Error downloading file: ${link.textContent.trim()}`);
                } finally {
                    loaderSpan.style.display = 'none';
                    // console.log('Loader hidden for individual download button');
                }
            });
            link.parentNode.appendChild(dlButton);
            // console.log('DL button appended for link:', link.textContent);
        });
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址