Sleazy Fork is available in English.

8Muses Downloader

Download comics from 8muses.com

질문, 리뷰하거나, 이 스크립트를 신고하세요.
  1. // ==UserScript==
  2. // @name 8Muses Downloader
  3. // @namespace https://github.com/Kayla355
  4. // @version 0.5.1
  5. // @description Download comics from 8muses.com
  6. // @author Kayla355
  7. // @match http://comics.8muses.com/comics/album/*
  8. // @match https://comics.8muses.com/comics/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://gitcdn.xyz/repo/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. // @history 0.3.2 Fixed the URL match since it was changed.
  25. // @history 0.4.0 Updated the script to work with the new use of Ractive.js on the 8muses website.
  26. // @history 0.4.1 Fixed some css to better match the site.
  27. // @history 0.5.0 Fixed Image links and more
  28. // ==/UserScript==
  29. cfg = new MonkeyConfig({
  30. title: '8Muses Downloader - Configuration',
  31. menuCommand: true,
  32. params: {
  33. single_file: {
  34. type: 'checkbox',
  35. default: true
  36. },
  37. compress_sub_folders: {
  38. type: 'checkbox',
  39. default: false
  40. }
  41. },
  42. onSave: setOptions
  43. });
  44.  
  45. var Settings = {
  46. singleFile: null,
  47. compressSubFolders: null,
  48. };
  49.  
  50. var zipArray = [];
  51. var containerArray = [];
  52. var downloadType;
  53. var progress = {
  54. pages: {
  55. current: 0,
  56. items: 0,
  57. },
  58. current: 0,
  59. items: 0,
  60. zips: 0
  61. };
  62.  
  63. function setOptions() {
  64. Settings.singleFile = cfg.get('single_file');
  65. Settings.compressSubFolders = cfg.get('compress_sub_folders');
  66. }
  67.  
  68. (function() {
  69. 'use strict';
  70. setOptions();
  71. init();
  72. })();
  73.  
  74. function init() {
  75. var imagebox = document.querySelector('.gallery .c-tile:not(.image-a)');
  76. if(imagebox) {
  77. var isImageAlbum = !!imagebox.href.match(/comics\/picture\//i);
  78. if(isImageAlbum) {
  79. createElements('single');
  80. } else {
  81. createElements('multi');
  82. }
  83. } else {
  84. setTimeout(init, 100);
  85. }
  86. }
  87.  
  88. function createElements(type) {
  89. downloadType = type || 'single';
  90. var downloadText = (downloadType == "multi") ? 'Download All':'Download';
  91. var div = document.createElement('div');
  92. div.className += 'download show-tablet show-desktop block';
  93. div.style = "background-color: #3a4050; border-left: 1px solid #1a1c22;";
  94. var a = document.createElement('a');
  95. a.href = "#";
  96. a.style = "color: #fff; text-decoration: none; padding: 15px 20px 15px 10px;";
  97. a.innerHTML = '<i class="fa fa-arrow-down icon-inline" style="color: #242730;"></i>'+ downloadText;
  98. a.onclick = downloadHandler;
  99. var bar = document.createElement('div');
  100. bar.innerHTML = `<div class="loading-bar" style="position: absolute; right: 0px; top: 50px; background-color: aliceblue; display: none;">
  101. <center class="value" style="position: absolute; left: 0px; right: 0px; color: #242730;">0%</center>
  102. <div class="progressbar" style="width: 0%; height:20px; background-color: #b1c6ff;"></div>
  103. </div>`;
  104. div.append(a);
  105. document.querySelector('#top-menu > div.top-menu-right').append(div);
  106. bar.querySelector('.loading-bar').style.width = document.querySelector('#top-menu > div.top-menu-right .download').clientWidth+'px';
  107. document.querySelector('#content').append(bar);
  108. }
  109.  
  110. function updateProgressbar(status, hide) {
  111. status = (typeof status === "string") ? status:status+'%';
  112. if(hide) {
  113. document.querySelector('.loading-bar').style.display = 'none';
  114. } else {
  115. document.querySelector('.loading-bar').style.display = '';
  116. document.querySelector('.loading-bar .value').innerText = status;
  117. document.querySelector('.loading-bar .progressbar').style.width = status;
  118. }
  119. }
  120.  
  121. function downloadHandler(e) {
  122. e.preventDefault();
  123. e.stopPropagation();
  124. if(document.querySelector('.loading-bar').style.display !== "none") return;
  125.  
  126. if(downloadType == "multi") {
  127. downloadAll();
  128. } else {
  129. downloadComic();
  130. }
  131. }
  132.  
  133. function downloadComic(container) {
  134. var imageContainers = (container) ? container:document.querySelectorAll('.gallery .c-tile:not(.image-a)');
  135. var images = [];
  136. var doneLength = 0;
  137. var isImageAlbum = !!imageContainers[0].attributes.href.value.match(/comics\/picture\//i);
  138.  
  139. if(!container) updateProgressbar(0);
  140. if(isImageAlbum) progress.pages.items += imageContainers.length;
  141. if(isImageAlbum) progress.items++;
  142.  
  143. for(var i=0; i < imageContainers.length; i++) {
  144. images.push({href: location.protocol +'//'+ location.hostname + imageContainers[i].attributes.href.value});
  145.  
  146. getPageImage(i, images[i], function(j, object) {
  147. images[j].path = object.path;
  148. images[j].name = object.name;
  149. images[j].imageHref = object.imageHref;
  150. images[j].blob = object.blob;
  151. doneLength++;
  152.  
  153. if(!container) {
  154. updateProgressbar(Math.round((doneLength/imageContainers.length)*100));
  155. } else if(isImageAlbum) {
  156. if(j === 0) progress.current++;
  157. progress.pages.current++;
  158. updateProgressbar(Math.round((progress.pages.current/progress.pages.items)*100));
  159. }
  160.  
  161. if(doneLength >= imageContainers.length) createZip(images);
  162. });
  163. }
  164. }
  165.  
  166. function downloadAll(container) {
  167. var itemContainers = (container) ? container:document.querySelectorAll('.gallery .c-tile:not(.image-a)');
  168. var pagination = document.querySelector('.pagination');
  169. // var pagination = false; //Disabled
  170. var items = [];
  171. var doneLength = 0;
  172.  
  173. var downloadFunc = function(albumContainer) {
  174. //console.log(albumContainer)
  175. var imagebox = albumContainer.querySelectorAll('.gallery .c-tile:not(.image-a)');
  176. var isImageAlbum = !!imagebox[0].attributes.href.value.match(/comics\/picture\//i);
  177.  
  178. if(isImageAlbum) {
  179. downloadComic(imagebox);
  180. } else {
  181. downloadAll(imagebox);
  182. }
  183. };
  184.  
  185. if(pagination && !container) {
  186. var lastHref = pagination.querySelector('span:last-child a').attributes.href.value;
  187. var pageCount = parseInt(lastHref.match(/[0-9]+$/)[0]);
  188. var urls = [];
  189. for(let i=1; i <= pageCount; i++) {
  190. urls.push(location.protocol +'//'+ location.hostname + lastHref.replace(/[0-9]+$/, i));
  191. }
  192. getImageAlbum(urls, downloadFunc);
  193. } else {
  194. if(!container) updateProgressbar(0);
  195.  
  196. for(let i=0; i < itemContainers.length; i++) {
  197. if(!itemContainers[i].attributes.href || itemContainers[i].attributes.href.value == "") continue;
  198. let href = location.protocol +'//'+ location.hostname + itemContainers[i].attributes.href.value;
  199. getImageAlbum(href, downloadFunc);
  200. }
  201. }
  202. }
  203.  
  204. function getImageAlbum(url, callback) {
  205. if(typeof url === "object" && url.length) {
  206. for(var i=0; i < url.length; i++) {
  207. getImageAlbum(url[i], function(pageContainer) {
  208. var items = pageContainer.querySelector('.gallery').innerHTML;
  209. containerArray.push(items);
  210.  
  211. if(containerArray.length >= url.length) {
  212. var container = document.implementation.createHTMLDocument().documentElement;
  213. container.innerHTML = '<div class="gallery">' + containerArray.join('') + '</div>';
  214. callback(container);
  215. }
  216. });
  217. }
  218. } else {
  219. var xhr = new XMLHttpRequest();
  220. xhr.open('GET', url);
  221. xhr.onload = function(e) {
  222. var container = document.implementation.createHTMLDocument().documentElement;
  223. container.innerHTML = xhr.responseText;
  224. callback(container);
  225. };
  226. xhr.send();
  227. }
  228. }
  229.  
  230. function getPageImage(i, image, callback) {
  231. var decodePublic = function(t) {
  232. return "!" === (e = t.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&")).charAt(0) ? e.substr(1).replace(/[\x21-\x7e]/g, function(t) {
  233. return String.fromCharCode(33 + (t.charCodeAt(0) + 14) % 94);
  234. }) : "";
  235. };
  236.  
  237. var object = {};
  238. var xhr = new XMLHttpRequest();
  239. xhr.open('GET', image.href);
  240. // xhr.responseType = 'blob';
  241. xhr.onload = function(e) {
  242. var container = document.implementation.createHTMLDocument().documentElement;
  243. container.innerHTML = xhr.responseText;
  244.  
  245. var data = JSON.parse(decodePublic(container.querySelector("#ractive-public").innerHTML.trim()));
  246. var ext = data.picture.normalizedPath.match(/\..*?$/i);
  247.  
  248. object.path = image.href.match(/^.*?(picture|album)\/.*?\/(.*\/).*$/i)[2]; // including author
  249. // object.path = image.href.match(/^.*?[0-9]+\/.*?\/(.*\/).*$/)[1]; // no author
  250. //object.name = container.querySelector('.top-menu-breadcrumb li:last-of-type').innerText.trim(); //+ container.querySelector('#imageName').value.match(/\.([0-9a-z]+)(?:[\?#]|$)/i)[0];
  251. //object.name = data.picture.name + ext;
  252. object.name = data.picture.name + ".jpg";
  253. //object.imageHref = 'https://www-8muses-com.cdn.ampproject.org/i/www.8muses.com/image/fl' + container.querySelector('#imageDir').value + container.querySelector('#imageName').value;
  254. //object.imageHref = 'https://www.8muses.com/image/fl/' + data.picture.publicUri + ext;
  255. object.imageHref = 'https://www.8muses.com/image/fl/' + data.picture.publicUri + ".jpg";
  256. console.log(object);
  257. getImageAsBlob(object.imageHref, function(blob) {
  258. if(!blob) return;
  259. object.blob = blob;
  260. callback(i, object);
  261. });
  262. };
  263. xhr.send();
  264. }
  265.  
  266. function getImageAsBlob(url, callback) {
  267. GM_xmlhttpRequest({
  268. url: url,
  269. method: 'GET',
  270. responseType: 'blob',
  271. onload: function(xhr) {
  272. var blob = xhr.response;
  273.  
  274. callback(blob);
  275. }
  276. });
  277.  
  278. // Non-GM CORS xhr request.
  279. // var xhr = new XMLHttpRequest();
  280. // xhr.open('GET', 'https://cors-anywhere.herokuapp.com/'+object.imageHref);
  281. // xhr.responseType = 'blob';
  282. // xhr.onload = function(e) {
  283. // var blob = xhr.response;
  284. // callback(blob);
  285. // }
  286. // xhr.send();
  287. }
  288.  
  289. function createZip(images) {
  290. var filename = getFileName(images[0].path);
  291. var zip = new JSZip();
  292.  
  293. // Generate single or multiple zip files.
  294. if(Settings.singleFile && progress.current > 0) {
  295. if(Settings.compressSubFolders) {
  296. for(let i=0; i < images.length; i++) {
  297. zip.file(images[i].name, images[i].blob);
  298. }
  299. generateZip(zip, filename, function(blob, filename) {
  300. zipArray.push({name: filename, blob: blob});
  301. progress.zips++;
  302. if(progress.zips === progress.items) {
  303. var singleZip = new JSZip();
  304. for(let i=0; i < zipArray.length; i++) {
  305. singleZip.file(zipArray[i].name, zipArray[i].blob);
  306. }
  307. generateZip(singleZip, filename.match(/\[(.*)\]/)[1], function(blob, filename) {
  308. saveAs(blob, filename);
  309. });
  310. }
  311. });
  312. } else {
  313. for(let i=0; i < images.length; i++) {
  314. zipArray.push({name: filename +'/'+ images[i].name, blob: images[i].blob});
  315. // zip.file(images[i].name, images[i].blob);
  316. }
  317.  
  318. if(progress.pages.current === progress.pages.items) {
  319. var singleZip = new JSZip();
  320. for(let i=0; i < zipArray.length; i++) {
  321. singleZip.file(zipArray[i].name, zipArray[i].blob);
  322. }
  323. generateZip(singleZip, filename.match(/\[(.*)\]/)[1], function(blob, filename) {
  324. saveAs(blob, filename);
  325. });
  326. }
  327. }
  328. } else {
  329. for(let i=0; i < images.length; i++) {
  330. zip.file(images[i].name, images[i].blob);
  331. }
  332. generateZip(zip, filename, function(blob, filename) {
  333. saveAs(blob, filename);
  334. });
  335. }
  336. }
  337.  
  338. // function generateZip(zip, filename, callback) {
  339. // zip.generateAsync({type:"blob"}).then(function (blob) {
  340.  
  341. // if(progress.pages.current === progress.pages.items) updateProgressbar('Done!');
  342. // if(typeof callback === 'function') callback(blob, filename+'.zip');
  343. // }, function (err) {
  344. // console.error('Error saving zip: ' +err);
  345. // });
  346. // }
  347.  
  348. function generateZip(zip, filename, callback) {
  349. zip.generateInternalStream({type:"blob", streamFiles: true})
  350. .accumulate(function updateCallback(metadata) {
  351. // console.log(metadata);
  352. updateProgressbar(metadata.percent);
  353. // metadata contains for example currentFile and percent, see the generateInternalStream doc.
  354. }).then(function (blob) {
  355. if(typeof callback === 'function') callback(blob, filename+'.zip');
  356. if(progress.pages.current === progress.pages.items) updateProgressbar('Done!');
  357. // data contains here the complete zip file as a uint8array (the type asked in generateInternalStream)
  358. });
  359. }
  360.  
  361. function getFileName(pathname) {
  362. var pathArray = pathname.replace(/\/$/, '').split('/');
  363. var filename = "";
  364.  
  365. for(var i=0; i<pathArray.length; i++) {
  366. let partialName;
  367.  
  368. if(i === 0) partialName = '['+ pathArray[i] +']';
  369. if(i === 1) partialName = pathArray[i];
  370. if(i >= 2) partialName = ' - '+ pathArray[i];
  371.  
  372. filename += partialName;
  373. }
  374.  
  375. return filename;
  376. }