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