Simfileshare Folder Downloader

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

当前为 2024-10-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Simfileshare Folder Downloader
  3. // @namespace https://github.com/dear-clouds/mio-userscripts
  4. // @version 1.0
  5. // @description Adds a button to download all files in a Simfileshare folder as a zip file and individual download buttons
  6. // @author Mio.
  7. // @supportURL https://github.com/dear-clouds/mio-userscripts/issues
  8. // @match https://simfileshare.net/folder/*
  9. // @grant none
  10. // @license MIT
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. window.addEventListener('load', () => {
  19. const MAX_CONCURRENT_DOWNLOADS = 8; // Control how many files to download concurrently here
  20.  
  21. // console.log('Page loaded, initializing script...');
  22.  
  23. // Button Download All
  24. const downloadAllButton = document.createElement('button');
  25. downloadAllButton.innerHTML = 'Download All Files as ZIP';
  26. const loaderSpanAll = document.createElement('span');
  27. loaderSpanAll.className = 'loader';
  28. loaderSpanAll.style.display = 'none';
  29. downloadAllButton.appendChild(loaderSpanAll);
  30. downloadAllButton.style.padding = '10px';
  31. downloadAllButton.style.backgroundColor = '#4CAF50';
  32. downloadAllButton.style.color = 'white';
  33. downloadAllButton.style.border = 'none';
  34. downloadAllButton.style.cursor = 'pointer';
  35. downloadAllButton.style.marginBottom = '10px';
  36. downloadAllButton.style.position = 'relative';
  37.  
  38. const h4Element = document.querySelector('h4');
  39. if (h4Element) {
  40. h4Element.parentNode.insertBefore(downloadAllButton, h4Element.nextSibling);
  41. // console.log('Download All button appended successfully');
  42. }
  43.  
  44. const loaderStyle = document.createElement('style');
  45. loaderStyle.innerHTML = `
  46. .loader {
  47. border: 4px solid #f3f3f3;
  48. border-top: 4px solid #3498db;
  49. border-radius: 50%;
  50. width: 16px;
  51. height: 16px;
  52. animation: spin 1s linear infinite;
  53. position: relative;
  54. margin-left: 10px;
  55. }
  56. @keyframes spin {
  57. 0% { transform: rotate(0deg); }
  58. 100% { transform: rotate(360deg); }
  59. }
  60. .dl-button {
  61. margin-left: 10px;
  62. padding: 4px 8px;
  63. background-color: #3498db;
  64. color: white;
  65. border: none;
  66. cursor: pointer;
  67. font-size: 0.9em;
  68. position: relative;
  69. }
  70. `;
  71. document.head.appendChild(loaderStyle);
  72. // console.log('Loader styles added');
  73.  
  74. downloadAllButton.addEventListener('click', async () => {
  75. console.log('Download All button clicked');
  76.  
  77. // Get all the download links in the folder
  78. const downloadLinks = document.querySelectorAll('a[href*="/download/"]');
  79. console.log(`Found ${downloadLinks.length} download links`);
  80.  
  81. if (downloadLinks.length === 0) {
  82. alert('No files found to download!');
  83. return;
  84. }
  85.  
  86. loaderSpanAll.style.display = 'inline-block';
  87. // console.log('Loader displayed for Download All button');
  88.  
  89. // Create a ZIP file with JSZip
  90. const zip = new JSZip();
  91. const folderTitle = h4Element.textContent.replace('Folder: ', '').trim();
  92. console.log(`Folder title determined: ${folderTitle}`);
  93.  
  94. // Function to fetch files with retries and concurrency limit
  95. const fetchFileWithRetry = async (url, retries = 20) => {
  96. for (let attempt = 0; attempt < retries; attempt++) {
  97. try {
  98. const directUrl = url.replace('/download/', '/cdn/download/') + '/?dl';
  99. console.log(`Fetching: ${directUrl} (Attempt ${attempt + 1})`);
  100. const response = await fetch(directUrl);
  101. if (response.ok) {
  102. console.log(`Successfully fetched: ${directUrl}`);
  103. return await response.blob();
  104. } else {
  105. console.warn(`Attempt ${attempt + 1} failed: ${response.statusText}`);
  106. }
  107. } catch (error) {
  108. console.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
  109. }
  110. await new Promise(resolve => setTimeout(resolve, 1000)); // Wait before retrying
  111. }
  112. throw new Error(`Failed to fetch file after ${retries} attempts: ${url}`);
  113. };
  114.  
  115. // Concurrency limiting function
  116. const limitConcurrency = async (limit, tasks) => {
  117. const results = [];
  118. const executing = [];
  119.  
  120. for (const task of tasks) {
  121. const p = task().then(result => {
  122. executing.splice(executing.indexOf(p), 1);
  123. return result;
  124. });
  125. results.push(p);
  126. executing.push(p);
  127.  
  128. if (executing.length >= limit) {
  129. await Promise.race(executing);
  130. }
  131. }
  132. return Promise.all(results);
  133. };
  134.  
  135. // Prepare a list of tasks to fetch each file
  136. const tasks = Array.from(downloadLinks).map(link => async () => {
  137. try {
  138. console.log(`Preparing to fetch file: ${link.href}`);
  139. const blob = await fetchFileWithRetry(link.href);
  140. const filename = link.textContent.trim() || `file_${Math.random().toString(36).substring(7)}`;
  141. console.log(`Adding file to ZIP: ${filename}`);
  142. zip.file(filename, blob);
  143. } catch (error) {
  144. console.error(error.message);
  145. }
  146. });
  147.  
  148. // Run tasks with controlled concurrency
  149. await limitConcurrency(MAX_CONCURRENT_DOWNLOADS, tasks);
  150. // console.log('All files fetched, generating ZIP');
  151.  
  152. // Generate ZIP and trigger download
  153. zip.generateAsync({ type: "blob" }).then(content => {
  154. saveAs(content, `${folderTitle}.zip`);
  155. loaderSpanAll.style.display = 'none';
  156. console.log('ZIP generated and download triggered');
  157. }).catch(error => {
  158. console.error('Error generating ZIP:', error);
  159. loaderSpanAll.style.display = 'none';
  160. alert('Error occurred while generating ZIP.');
  161. });
  162. });
  163.  
  164. // Add individual download buttons next to each file link
  165. const fileLinks = document.querySelectorAll('tr td a[href*="/download/"]');
  166. fileLinks.forEach(link => {
  167. const dlButton = document.createElement('button');
  168. dlButton.innerHTML = 'DL';
  169. const loaderSpan = document.createElement('span');
  170. loaderSpan.className = 'loader';
  171. loaderSpan.style.display = 'none';
  172. dlButton.appendChild(loaderSpan);
  173. dlButton.className = 'dl-button';
  174. dlButton.addEventListener('click', async (e) => {
  175. e.preventDefault();
  176. console.log(`Individual download button clicked for link: ${link.href}`);
  177. loaderSpan.style.display = 'inline-block';
  178. // console.log('Loader displayed for individual download button');
  179. try {
  180. // First visit the link to prepare the direct download
  181. await fetch(link.href, { method: 'GET', mode: 'no-cors' });
  182. const directUrl = link.href.replace('/download/', '/cdn/download/') + '/?dl';
  183. console.log(`Fetching direct URL: ${directUrl}`);
  184. const response = await fetch(directUrl);
  185. if (!response.ok) {
  186. throw new Error(`Failed to download: ${response.statusText}`);
  187. }
  188. const blob = await response.blob();
  189. const filename = link.textContent.trim();
  190. console.log(`Successfully downloaded file: ${filename}`);
  191. const a = document.createElement('a');
  192. a.href = URL.createObjectURL(blob);
  193. a.download = filename;
  194. document.body.appendChild(a);
  195. a.click();
  196. document.body.removeChild(a);
  197. console.log(`File download triggered: ${filename}`);
  198. } catch (error) {
  199. console.error('Error downloading file:', error);
  200. alert(`Error downloading file: ${link.textContent.trim()}`);
  201. } finally {
  202. loaderSpan.style.display = 'none';
  203. // console.log('Loader hidden for individual download button');
  204. }
  205. });
  206. link.parentNode.appendChild(dlButton);
  207. // console.log('DL button appended for link:', link.textContent);
  208. });
  209. });
  210. })();