MRM Downloader

Download video and bulk images from myreadingmanga manga/doujin page.

当前为 2024-11-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MRM Downloader
  3. // @namespace https://nyt92.eu.org
  4. // @version 2024-11-21
  5. // @description Download video and bulk images from myreadingmanga manga/doujin page.
  6. // @author nyt92
  7. // @match https://myreadingmanga.info/*
  8. // @exclude https://myreadingmanga.info/about/
  9. // @exclude https://myreadingmanga.info/cats/*
  10. // @exclude https://myreadingmanga.info/pairing/*
  11. // @exclude https://myreadingmanga.info/group/*
  12. // @exclude https://myreadingmanga.info/privacy-policy/
  13. // @exclude https://myreadingmanga.info/dmca-notice/
  14. // @exclude https://myreadingmanga.info/contact/
  15. // @exclude https://myreadingmanga.info/terms-service/
  16. // @exclude https://myreadingmanga.info/sitemap/
  17. // @exclude https://myreadingmanga.info/my-bookmark/
  18. // @exclude https://myreadingmanga.info/tag/*
  19. // @exclude https://myreadingmanga.info/genre/*
  20. // @exclude https://myreadingmanga.info/status/*
  21. // @exclude https://myreadingmanga.info/lang/*
  22. // @exclude https://myreadingmanga.info/yaoi-manga/*
  23. // @exclude https://myreadingmanga.info/manhwa/*
  24. // @supportURL https://gist.github.com/NYT92/1247c6421ad95bfc2a0b10d912fb56f0
  25. // @icon https://www.google.com/s2/favicons?sz=64&domain=myreadingmanga.info
  26. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
  27. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
  28. // @grant GM_cookie
  29. // @grant GM_xmlhttpRequest
  30. // @license GPLv3
  31. // ==/UserScript==
  32.  
  33. // THIS USERSCRIPT IS LICENSE UNDER THE GPLv3 License
  34.  
  35. (function() {
  36. 'use strict';
  37. const title = document.querySelector('.entry-header h1.entry-title')?.textContent.trim() || 'Untitled';
  38.  
  39. const imageDlBtn = document.createElement('button');
  40. imageDlBtn.id = 'downloadImagesBtn';
  41. imageDlBtn.textContent = 'Download Images (.zip)';
  42. imageDlBtn.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
  43.  
  44. const videoDlBtn = document.createElement('button');
  45. videoDlBtn.id = 'downloadVideoBtn';
  46. videoDlBtn.textContent = 'Download Video';
  47. videoDlBtn.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
  48.  
  49. const progressBar = document.createElement('div');
  50. progressBar.id = 'downloadProgress';
  51. progressBar.style.cssText = 'position: fixed; top:80px; right: 10px; width: 235px; right: 10px; height: 20px; background-color: #f0f0f0; display: none; z-index: 9999;';
  52.  
  53. const progressInner = document.createElement('div');
  54. progressInner.style.cssText = 'width: 0%; height: 100%; background-color: #4CAF50; transition: width 0.5s;';
  55. progressBar.appendChild(progressInner);
  56.  
  57. const progressText = document.createElement('div');
  58. progressText.style.cssText = 'position: fixed; top: 105px; right: 10px; z-index: 9999; display: none;';
  59. progressText.textContent = 'Preparing download...';
  60.  
  61. const checkIfVid = document.querySelector(".entry-categories a");
  62.  
  63. if (!checkIfVid) {
  64. console.log("no media found!")
  65. } else if (checkIfVid.textContent.includes("Video")) {
  66. document.body.appendChild(videoDlBtn);
  67. } else {
  68. document.body.appendChild(imageDlBtn);
  69. }
  70. document.body.appendChild(progressBar);
  71. document.body.appendChild(progressText);
  72.  
  73. imageDlBtn.addEventListener('click', function() {
  74. imageDlBtn.disabled = true;
  75. imageDlBtn.textContent = 'Downloading...';
  76.  
  77. const images = document.querySelectorAll('.img-myreadingmanga');
  78.  
  79. let imageSources = [];
  80.  
  81. imageSources = Array.from(images).map(img => img.dataset.src).filter(Boolean);
  82.  
  83. if (imageSources.length === 0) {
  84. imageSources = Array.from(images).map(img => img.src).filter(Boolean);
  85. }
  86.  
  87. if (imageSources.length === 0) {
  88. const nestedImages = document.querySelectorAll('.img-myreadingmanga img');
  89. imageSources = Array.from(nestedImages).map(img => img.src || img.dataset.src).filter(Boolean);
  90. }
  91.  
  92. if (imageSources.length === 0) {
  93. alert("No images found on this page. Check the console for debugging information.");
  94. imageDlBtn.disabled = false;
  95. imageDlBtn.textContent = 'Download Images (.zip)';
  96. return;
  97. }
  98.  
  99. const pageElement = document.querySelector('.post-page-numbers.current');
  100. const page = pageElement ? pageElement.textContent.trim() : '1';
  101.  
  102. const zip = new JSZip();
  103.  
  104. progressBar.style.display = 'block';
  105. progressText.style.display = 'block';
  106. progressInner.style.width = '0%';
  107.  
  108. function getExtensionFromMimeType(mimeType) {
  109. const mimeToExt = {
  110. 'image/jpeg': 'jpg',
  111. 'image/png': 'png',
  112. 'image/gif': 'gif',
  113. 'image/webp': 'webp'
  114. };
  115. return mimeToExt[mimeType] || 'jpg';
  116. }
  117.  
  118. function addImageToZip(src, index) {
  119. return new Promise((resolve, reject) => {
  120. progressText.textContent = `Downloading image ${index + 1} of ${imageSources.length}...`;
  121. GM_xmlhttpRequest({
  122. method: "GET",
  123. url: src,
  124. responseType: "arraybuffer",
  125. onload: function(response) {
  126. try {
  127. const arrayBuffer = response.response;
  128. const byteArray = new Uint8Array(arrayBuffer);
  129. const blob = new Blob([byteArray], {type: response.responseHeaders.match(/Content-Type: (.*)/i)[1]});
  130. const ext = getExtensionFromMimeType(blob.type);
  131. const fileName = `image_${index + 1}.${ext}`;
  132. zip.file(fileName, blob, {binary: true});
  133. console.log(`Added ${fileName} to ZIP (${blob.size} bytes, type: ${blob.type})`);
  134. const progress = ((index + 1) / imageSources.length) * 100;
  135. progressInner.style.width = `${progress}%`;
  136. resolve();
  137. } catch (error) {
  138. console.error(`Error processing ${src}:`, error);
  139. reject(error);
  140. }
  141. },
  142. onerror: function(error) {
  143. console.error(`Error fetching ${src}:`, error);
  144. reject(error);
  145. }
  146. });
  147. });
  148. }
  149.  
  150. Promise.all(imageSources.map(addImageToZip))
  151. .then(() => {
  152. progressText.textContent = 'Creating ZIP file...';
  153. return zip.generateAsync({type: "blob"});
  154. })
  155. .then(function(content) {
  156. const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  157. const fileName = `${safeTitle}_ch${page}.zip`;
  158. saveAs(content, fileName);
  159. console.log("ZIP file saved successfully");
  160. progressBar.style.display = 'none';
  161. progressText.style.display = 'none';
  162. imageDlBtn.disabled = false;
  163. imageDlBtn.textContent = 'Download Images (.zip)';
  164. })
  165. .catch(error => {
  166. console.error("Error creating ZIP file:", error);
  167. alert("An error occurred while creating the ZIP file. Please check the console for details.");
  168. progressBar.style.display = 'none';
  169. progressText.style.display = 'none';
  170. imageDlBtn.disabled = false;
  171. imageDlBtn.textContent = 'Download Images (.zip)';
  172. });
  173. });
  174. videoDlBtn.addEventListener('click', function() {
  175. videoDlBtn.disabled = true;
  176. videoDlBtn.textContent = 'Downloading...';
  177.  
  178. const videoElement = document.querySelector("#MRM_video > video > source");
  179. if (!videoElement) {
  180. alert("No video found on this page.");
  181. videoDlBtn.disabled = false;
  182. videoDlBtn.textContent = 'Download Video';
  183. return;
  184. }
  185.  
  186. const videoSrc = videoElement.src;
  187. if (!videoSrc) {
  188. alert("Unable to find video source.");
  189. videoDlBtn.disabled = false;
  190. videoDlBtn.textContent = 'Download Video';
  191. return;
  192. }
  193.  
  194. progressBar.style.display = 'block';
  195. progressText.style.display = 'block';
  196. progressText.textContent = 'Starting video download...';
  197. progressInner.style.width = '0%';
  198.  
  199. GM_xmlhttpRequest({
  200. method: "GET",
  201. url: videoSrc,
  202. responseType: "arraybuffer",
  203. onprogress: function(progress) {
  204. if (progress.lengthComputable) {
  205. const percentComplete = (progress.loaded / progress.total) * 100;
  206. progressInner.style.width = percentComplete + '%';
  207. const downloadedMB = (progress.loaded / (1024 * 1024)).toFixed(2);
  208. const totalMB = (progress.total / (1024 * 1024)).toFixed(2);
  209. progressText.textContent = `Downloaded: ${downloadedMB}MB / ${totalMB}MB`;
  210. }
  211. },
  212. onload: function(response) {
  213. const blob = new Blob([response.response], {type: 'video/mp4'});
  214. const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
  215. const fileName = `${safeTitle}.mp4`;
  216. saveAs(blob, fileName);
  217. console.log("Video downloaded successfully");
  218. progressBar.style.display = 'none';
  219. progressText.style.display = 'none';
  220. videoDlBtn.disabled = false;
  221. videoDlBtn.textContent = 'Download Video';
  222. },
  223. onerror: function(error) {
  224. console.error("Error downloading video:", error);
  225. alert("An error occurred while downloading the video. Please check the console for details.");
  226. progressBar.style.display = 'none';
  227. progressText.style.display = 'none';
  228. videoDlBtn.disabled = false;
  229. videoDlBtn.textContent = 'Download Video';
  230. }
  231. });
  232. })
  233. })();