8Muses Downloader

Download comics from 8muses.com

Versión del día 30/11/2017. Echa un vistazo a la versión más reciente.

  1. // ==UserScript==
  2. // @name 8Muses Downloader
  3. // @namespace https://github.com/Kayla355
  4. // @version 0.3.1
  5. // @description Download comics from 8muses.com
  6. // @author Kayla355
  7. // @match http://www.8muses.com/comix/album/*
  8. // @match https://www.8muses.com/comix/album/*
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_registerMenuCommand
  14. // @run-at document-idle
  15. // @icon https://www.8muses.com/favicon.ico
  16. // @require https://cdn.jsdelivr.net/jszip/3.1.3/jszip.min.js
  17. // @require https://cdn.jsdelivr.net/filesaver.js/1.3.3/FileSaver.min.js
  18. // @require https://cdn.rawgit.com/Kayla355/MonkeyConfig/d152bb448db130169dbd659b28375ae96e4c482d/monkeyconfig.js
  19. // @history 0.2.0 'Download all', now has an option to compress all the individual .zip files into one zip with sub-folders. By default this will be on.
  20. // @history 0.2.1 Added an option to compress the subfolders created from the single file download option.
  21. // @history 0.2.2 Fixed a bug where it would trigger the download multiple times when the "single file" option was enabled and the "compress sub folders" option was not.
  22. // @history 0.3.0 ALso added the basis for download trying to download something with pagination. However, this is disabled until I solve the issue of running out of memory while doing it.
  23. // @history 0.3.1 Fixed an issue caused by classnames being changed on the site.
  24. // ==/UserScript==
  25. cfg = new MonkeyConfig({
  26. title: '8Muses Downloader - Configuration',
  27. menuCommand: true,
  28. params: {
  29. single_file: {
  30. type: 'checkbox',
  31. default: true
  32. },
  33. compress_sub_folders: {
  34. type: 'checkbox',
  35. default: false
  36. }
  37. },
  38. onSave: setOptions
  39. });
  40.  
  41. var Settings = {
  42. singleFile: null,
  43. compressSubFolders: null,
  44. };
  45.  
  46. var zipArray = [];
  47. var containerArray = [];
  48. var downloadType;
  49. var progress = {
  50. pages: {
  51. current: 0,
  52. items: 0,
  53. },
  54. current: 0,
  55. items: 0,
  56. zips: 0
  57. };
  58.  
  59. function setOptions() {
  60. Settings.singleFile = cfg.get('single_file');
  61. Settings.compressSubFolders = cfg.get('compress_sub_folders');
  62. }
  63.  
  64. (function() {
  65. 'use strict';
  66. setOptions();
  67. init();
  68. })();
  69.  
  70. function init() {
  71. var imagebox = document.querySelector('.gallery .c-tile:not(.image-a)');
  72. if(imagebox) {
  73. var isImageAlbum = !!imagebox.href.match(/comix\/picture\//i);
  74. if(isImageAlbum) {
  75. createElements('single');
  76. } else {
  77. createElements('multi');
  78. }
  79. } else {
  80. setTimeout(init, 100);
  81. }
  82. }
  83.  
  84. function createElements(type) {
  85. downloadType = type || 'single';
  86. var downloadText = (downloadType == "multi") ? 'Download All':'Download';
  87. var div = document.createElement('div');
  88. div.className += 'download show-tablet show-desktop block';
  89. div.style = "background-color: #3a4050; margin-right: -20px; margin-left: 21px;";
  90. var a = document.createElement('a');
  91. a.href = "#";
  92. a.style = "color: #fff; text-decoration: none; padding: 15px 20px 15px 10px;";
  93. a.innerHTML = '<i class="fa fa-arrow-down icon-inline" style="color: #242730;"></i>'+ downloadText;
  94. a.onclick = downloadHandler;
  95. var bar = document.createElement('div');
  96. bar.innerHTML = `<div class="loading-bar" style="position: absolute; right: 0px; top: 50px; background-color: aliceblue; display: none;">
  97. <center class="value" style="position: absolute; left: 0px; right: 0px; color: #242730;">0%</center>
  98. <div class="progressbar" style="width: 0%; height:20px; background-color: #b1c6ff;"></div>
  99. </div>`;
  100. div.append(a);
  101. document.querySelector('#top-menu > div.top-menu-right').append(div);
  102. bar.querySelector('.loading-bar').style.width = document.querySelector('#top-menu > div.top-menu-right .download').clientWidth+'px';
  103. document.querySelector('#content').append(bar);
  104. }
  105.  
  106. function updateProgressbar(status, hide) {
  107. status = (typeof status === "string") ? status:status+'%';
  108. if(hide) {
  109. document.querySelector('.loading-bar').style.display = 'none';
  110. } else {
  111. document.querySelector('.loading-bar').style.display = '';
  112. document.querySelector('.loading-bar .value').innerText = status;
  113. document.querySelector('.loading-bar .progressbar').style.width = status;
  114. }
  115. }
  116.  
  117. function downloadHandler(e) {
  118. if(document.querySelector('.loading-bar').style.display !== "none") return;
  119.  
  120. if(downloadType == "multi") {
  121. downloadAll();
  122. } else {
  123. downloadComic();
  124. }
  125. }
  126.  
  127. function downloadComic(container) {
  128. var imageContainers = (container) ? container:document.querySelectorAll('.gallery .c-tile:not(.image-a)');
  129. var images = [];
  130. var doneLength = 0;
  131. var isImageAlbum = !!imageContainers[0].attributes.href.value.match(/comix\/picture\//i);
  132.  
  133. if(!container) updateProgressbar(0);
  134. if(isImageAlbum) progress.pages.items += imageContainers.length;
  135. if(isImageAlbum) progress.items++;
  136.  
  137. for(var i=0; i < imageContainers.length; i++) {
  138. images.push({href: location.protocol +'//'+ location.hostname + imageContainers[i].attributes.href.value});
  139.  
  140. getPageImage(i, images[i], function(j, object) {
  141. images[j].path = object.path;
  142. images[j].name = object.name;
  143. images[j].imageHref = object.imageHref;
  144. images[j].blob = object.blob;
  145. doneLength++;
  146.  
  147. if(!container) {
  148. updateProgressbar(Math.round((doneLength/imageContainers.length)*100));
  149. } else if(isImageAlbum) {
  150. if(j === 0) progress.current++;
  151. progress.pages.current++;
  152. updateProgressbar(Math.round((progress.pages.current/progress.pages.items)*100));
  153. }
  154.  
  155. if(doneLength >= imageContainers.length) createZip(images);
  156. });
  157. }
  158. }
  159.  
  160. function downloadAll(container) {
  161. var itemContainers = (container) ? container:document.querySelectorAll('.gallery .c-tile:not(.image-a)');
  162. var pagination = document.querySelector('.pagination');
  163. // var pagination = false; //Disabled
  164. var items = [];
  165. var doneLength = 0;
  166.  
  167. var downloadFunc = function(albumContainer) {
  168. var imagebox = albumContainer.querySelectorAll('.gallery .c-tile:not(.image-a)');
  169. var isImageAlbum = !!imagebox[0].attributes.href.value.match(/comix\/picture\//i);
  170.  
  171. if(isImageAlbum) {
  172. downloadComic(imagebox);
  173. } else {
  174. downloadAll(imagebox);
  175. }
  176. }
  177.  
  178. if(pagination && !container) {
  179. var lastHref = pagination.querySelector('.last a').attributes.href.value;
  180. var pageCount = parseInt(lastHref.match(/[0-9]+$/)[0]);
  181. var urls = [];
  182. for(var i=1; i <= pageCount; i++) {
  183. urls.push(location.protocol +'//'+ location.hostname + lastHref.replace(/[0-9]+$/, i));
  184. }
  185. getImageAlbum(urls, downloadFunc);
  186. } else {
  187. if(!container) updateProgressbar(0);
  188.  
  189. for(var i=0; i < itemContainers.length; i++) {
  190. let href = location.protocol +'//'+ location.hostname + itemContainers[i].attributes.href.value;
  191. getImageAlbum(href, downloadFunc);
  192. }
  193. }
  194. }
  195.  
  196. function getImageAlbum(url, callback) {
  197. if(typeof url === "object" && url.length) {
  198. for(var i=0; i < url.length; i++) {
  199. getImageAlbum(url[i], function(pageContainer) {
  200. var items = pageContainer.querySelector('.gallery').innerHTML;
  201. containerArray.push(items);
  202. if(containerArray.length >= url.length) {
  203. var container = document.implementation.createHTMLDocument().documentElement;
  204. container.innerHTML = '<div class="gallery">' + containerArray.join('') + '</div>';
  205. callback(container);
  206. }
  207. });
  208. }
  209. } else {
  210. var xhr = new XMLHttpRequest();
  211. xhr.open('GET', url);
  212. xhr.onload = function(e) {
  213. var container = document.implementation.createHTMLDocument().documentElement;
  214. container.innerHTML = xhr.responseText;
  215. callback(container);
  216. };
  217. xhr.send();
  218. }
  219. }
  220.  
  221. function getPageImage(i, image, callback) {
  222. var object = {};
  223. var xhr = new XMLHttpRequest();
  224. xhr.open('GET', image.href);
  225. // xhr.responseType = 'blob';
  226. xhr.onload = function(e) {
  227. var container = document.implementation.createHTMLDocument().documentElement;
  228. container.innerHTML = xhr.responseText;
  229.  
  230. object.path = image.href.match(/^.*?(picture|album)\/.*?\/(.*\/).*$/i)[2]; // including author
  231. // object.path = image.href.match(/^.*?[0-9]+\/.*?\/(.*\/).*$/)[1]; // no author
  232. object.name = container.querySelector('.top-menu-breadcrumb li:last-of-type').innerText.trim() + container.querySelector('#imageName').value.match(/\.([0-9a-z]+)(?:[\?#]|$)/i)[0];
  233. object.imageHref = 'https://www-8muses-com.cdn.ampproject.org/i/www.8muses.com/image/fl' + container.querySelector('#imageDir').value + container.querySelector('#imageName').value;
  234. console.log(object);
  235. getImageAsBlob(object.imageHref, function(blob) {
  236. if(!blob) return;
  237. object.blob = blob;
  238. callback(i, object);
  239. });
  240. };
  241. xhr.send();
  242. }
  243.  
  244. function getImageAsBlob(url, callback) {
  245. GM_xmlhttpRequest({
  246. url: url,
  247. method: 'GET',
  248. responseType: 'blob',
  249. onload: function(xhr) {
  250. var blob = xhr.response;
  251.  
  252. callback(blob);
  253. }
  254. });
  255.  
  256. // Non-GM CORS xhr request.
  257. // var xhr = new XMLHttpRequest();
  258. // xhr.open('GET', 'https://cors-anywhere.herokuapp.com/'+object.imageHref);
  259. // xhr.responseType = 'blob';
  260. // xhr.onload = function(e) {
  261. // var blob = xhr.response;
  262. // callback(blob);
  263. // }
  264. // xhr.send();
  265. }
  266.  
  267. function createZip(images) {
  268. var filename = getFileName(images[0].path);
  269.  
  270. // Generate single or multiple zip files.
  271. if(Settings.singleFile && progress.current > 0) {
  272. if(Settings.compressSubFolders) {
  273. var zip = new JSZip();
  274. for(let i=0; i < images.length; i++) {
  275. zip.file(images[i].name, images[i].blob);
  276. }
  277. generateZip(zip, filename, function(blob, filename) {
  278. zipArray.push({name: filename, blob: blob});
  279. progress.zips++;
  280. if(progress.zips === progress.items) {
  281. var singleZip = new JSZip();
  282. for(let i=0; i < zipArray.length; i++) {
  283. singleZip.file(zipArray[i].name, zipArray[i].blob);
  284. }
  285. generateZip(singleZip, filename.match(/\[(.*)\]/)[1], function(blob, filename) {
  286. saveAs(blob, filename);
  287. });
  288. }
  289. });
  290. } else {
  291. for(let i=0; i < images.length; i++) {
  292. zipArray.push({name: filename +'/'+ images[i].name, blob: images[i].blob});
  293. // zip.file(images[i].name, images[i].blob);
  294. }
  295.  
  296. if(progress.pages.current === progress.pages.items) {
  297. var singleZip = new JSZip();
  298. for(let i=0; i < zipArray.length; i++) {
  299. singleZip.file(zipArray[i].name, zipArray[i].blob);
  300. }
  301. generateZip(singleZip, filename.match(/\[(.*)\]/)[1], function(blob, filename) {
  302. saveAs(blob, filename);
  303. });
  304. }
  305. }
  306. } else {
  307. var zip = new JSZip();
  308. for(let i=0; i < images.length; i++) {
  309. zip.file(images[i].name, images[i].blob);
  310. }
  311. generateZip(zip, filename, function(blob, filename) {
  312. saveAs(blob, filename);
  313. });
  314. }
  315. }
  316.  
  317. // function generateZip(zip, filename, callback) {
  318. // zip.generateAsync({type:"blob"}).then(function (blob) {
  319.  
  320. // if(progress.pages.current === progress.pages.items) updateProgressbar('Done!');
  321. // if(typeof callback === 'function') callback(blob, filename+'.zip');
  322. // }, function (err) {
  323. // console.error('Error saving zip: ' +err);
  324. // });
  325. // }
  326.  
  327. function generateZip(zip, filename, callback) {
  328. zip.generateInternalStream({type:"blob", streamFiles: true})
  329. .accumulate(function updateCallback(metadata) {
  330. // console.log(metadata);
  331. updateProgressbar(metadata.percent);
  332. // metadata contains for example currentFile and percent, see the generateInternalStream doc.
  333. }).then(function (blob) {
  334. if(typeof callback === 'function') callback(blob, filename+'.zip');
  335. if(progress.pages.current === progress.pages.items) updateProgressbar('Done!');
  336. // data contains here the complete zip file as a uint8array (the type asked in generateInternalStream)
  337. });
  338. }
  339.  
  340. function getFileName(pathname) {
  341. var pathArray = pathname.replace(/\/$/, '').split('/');
  342. var filename = "";
  343.  
  344. for(var i=0; i<pathArray.length; i++) {
  345. let partialName;
  346.  
  347. if(i === 0) partialName = '['+ pathArray[i] +']';
  348. if(i === 1) partialName = pathArray[i];
  349. if(i >= 2) partialName = ' - '+ pathArray[i];
  350.  
  351. filename += partialName;
  352. }
  353.  
  354. return filename;
  355. }