mangatoto.com, bato.to Downloader

Download chapter from mangatoto.com, bato.to. Based on waifubitches.com downloader

  1. // ==UserScript==
  2. // @name mangatoto.com, bato.to Downloader
  3. // @description Download chapter from mangatoto.com, bato.to. Based on waifubitches.com downloader
  4. // @namespace chimichanga
  5. // @author chimichanga
  6. // @icon https://bato.to/amsta/img/batoto/favicon.ico
  7. // @version 2.0
  8. // @license MIT
  9. // @match https://bato.to/chapter/*
  10. // @match https://bato.to/title/*
  11. // @match https://mangatoto.com/chapter/*
  12. // @match https://mangatoto.com/title/*
  13. // @require https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.7.0.min.js
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.4/FileSaver.min.js
  16. // @noframes
  17. // @connect self
  18. // @connect batcg.org
  19. // @run-at document-idle
  20. // @grant GM_xmlhttpRequest
  21. // ==/UserScript==
  22.  
  23. // based originally on the 8muses.com downloader script
  24. var downBtn;
  25. var downStatus;
  26. var zipFile;
  27. const State = { NOT_STARTED: Symbol('not_started'), DOWNLOADING: Symbol('downloading'), COMPRESSING: Symbol('compressing'), DONE: Symbol('done') };
  28. var state = State.NOT_STARTED;
  29. var detectSourcesInterval;
  30.  
  31. var zip = new JSZip();
  32. var resultBlob;
  33. var sources = [];
  34. var thumbnails = [];
  35. var completed = 0;
  36. var failed = 0;
  37.  
  38. var V3 = $('astro-island').length; // page version
  39.  
  40. $(document).ready(function () {
  41. console.log(V3);
  42. downBtn = $(`<a class="btn btn-outline ${V3 ? `btn-sm w-full` : `btn-md col-24`} btn-warning rounded "><i ${V3 ? 'style="font-family:FontAwesome5_Solid;font-weight:normal;font-style:normal"' : 'class="fas fa-fw fa-download"'}>${V3 ? '':''}</i> <span></span></a>`)
  43. let btnWrapper = $(`<div class="mt-3"></div>`).append(downBtn);
  44. downStatus = $(downBtn).find('span');
  45.  
  46. $('div#container div.row').append(btnWrapper);
  47. $('#app-wrapper > div:nth-child(3) > div').append(btnWrapper);
  48.  
  49. zipFile = document.title + '.zip';
  50. if (V3) zipFile = zipFile.replace(" - Read Free Manga Online at Bato.To", "");
  51.  
  52. detectSources();
  53. detectSourcesInterval = setInterval(detectSources, 1000);
  54.  
  55. $(downBtn).click(download);
  56. });
  57.  
  58. function detectSources() {
  59. let detectedSources = $(V3 ? '[name=image-item] img' : 'img.page-img').map((_, { src }) => src).get();
  60. if(detectedSources.length > sources.length) {
  61. sources = detectedSources;
  62. updateState(State.NOT_STARTED);
  63. } else if (state == State.NOT_STARTED) {
  64. updateState(State.NOT_STARTED, 'detecting...');
  65. }
  66. }
  67.  
  68. function updateState(newState, status) {
  69. state = newState;
  70. $(downBtn).toggleClass('btn-success', state == State.DONE);
  71. $(downBtn).toggleClass('btn-warning', state == State.NOT_STARTED || state == State.COMPRESSING || state == State.DOWNLOADING);
  72. $(downBtn).toggleClass('btn-danger', failed > 0);
  73.  
  74. function getMsg() {
  75. let failedMsg = failed > 0 ? ` (${failed} failed)` : '';
  76. let statusMsg = status ? ` ${status}` : '';
  77.  
  78. switch(state){
  79. case State.NOT_STARTED:
  80. return `DOWNLOAD (${sources.length || status})`;
  81. case State.DOWNLOADING:
  82. return `DOWNLOADING${statusMsg}${failedMsg}`;
  83. case State.COMPRESSING:
  84. return `COMPRESSING${statusMsg}${failedMsg}`;
  85. case State.DONE:
  86. return `ZIP READY`;
  87. }
  88. }
  89.  
  90. $(downStatus).html(getMsg());
  91. }
  92.  
  93. function download() {
  94. if (state == State.DONE && failed == 0) {
  95. saveZip();
  96. return;
  97. }
  98.  
  99. if (state == State.DOWNLOADING || state == State.COMPRESSING)
  100. return;
  101.  
  102. updateState(State.DOWNLOADING);
  103.  
  104. completed = 0;
  105. failed = 0;
  106.  
  107. let padLength = Math.floor(Math.log10(sources.length))+1;
  108.  
  109. Promise.allSettled(
  110. sources.map((url, i) =>
  111. fetch(url).then(({ response, url }) => {
  112. completed++;
  113. updateState(State.DOWNLOADING, `${completed}/${sources.length}`);
  114. let fileName = `${i+1}`.padStart(padLength, 0) + `.webp`;
  115. zip.file(fileName, response);
  116. }).catch((cause) => {
  117. console.log(`can't fetch image ${i}, ${cause}: ${url}`);
  118. failed++;
  119. }))).then(saveZip);
  120. }
  121.  
  122. function fetch(url) {
  123. return new Promise((resolve, reject) => GM_xmlhttpRequest({
  124. method: 'GET',
  125. url: url,
  126. responseType: 'arraybuffer',
  127. onload: ({ response, status }) => status == 200 ? resolve({ response: response, url: url }) : reject('missing'),
  128. onerror: () => reject('error'),
  129. onabort: () => reject('abort'),
  130. ontimeout: () => reject('timeout'),
  131. }));
  132. }
  133.  
  134. function saveZip() {
  135. if (state == State.DONE) {
  136. saveAs(resultBlob, zipFile);
  137. return;
  138. }
  139.  
  140. zip.generateAsync(
  141. { type: 'blob' },
  142. ({ percent }) => updateState(State.COMPRESSING, `${percent.toFixed(2)}%`)
  143. ).then(function (blob) {
  144. updateState(State.DONE, `${completed}/${sources.length}`);
  145. resultBlob = blob;
  146. saveAs(resultBlob, zipFile);
  147. });
  148. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址