您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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); }); }); })();