Simfileshare Folder Downloader

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

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