n210-dl

Script used to download manga R18 on some domain of nhentai

As of 2024-04-15. See the latest version.

  1. // ==UserScript==
  2. // @name n210-dl
  3. // @namespace https://gist.github.com/hiroshil
  4. // @version 0.1.0
  5. // @description Script used to download manga R18 on some domain of nhentai
  6. // @license MIT
  7. // @author hiroshil
  8. // @source https://gist.github.com/hiroshil/86723bc557efa88931cf6b135e42a2b2
  9. // @match http*://nhentai.to/g/*
  10. // @match http*://nhentai.xxx/g/*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=nhentai.to
  12. // ==/UserScript==
  13. /* jshint esversion: 8 */
  14.  
  15. //https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
  16. //https://stackoverflow.com/questions/30008114/how-do-i-promisify-native-xhr
  17. //https://stackoverflow.com/questions/5582574/how-to-check-if-a-string-contains-text-from-an-array-of-substrings-in-javascript
  18.  
  19. function waitForElm(selector) {
  20. return new Promise(resolve => {
  21. if (document.querySelectorAll(selector).length) {
  22. return resolve(document.querySelectorAll(selector));
  23. }
  24. const observer = new MutationObserver(mutations => {
  25. if (document.querySelectorAll(selector).length) {
  26. resolve(document.querySelectorAll(selector));
  27. observer.disconnect();
  28. }
  29. });
  30. observer.observe(document.body, {
  31. childList: true,
  32. subtree: true
  33. });
  34. });
  35. }
  36. function waitForXHRRequest(url,method,responseType) {
  37. return new Promise( (resolve, reject) => {
  38. var res = new XMLHttpRequest();
  39. res.responseType = responseType;
  40. res.open(method, url, true);
  41. res.send();
  42. res.onload = () => {
  43. if (res.status >= 200 && res.status < 400) {
  44. resolve(res.response);
  45. } else {
  46. reject(false);
  47. }
  48. }
  49. });
  50.  
  51. }
  52. function sleeper(ms) {
  53. return function(x) {
  54. return new Promise(resolve => setTimeout(() => resolve(x), ms));
  55. };
  56. }
  57. function parseStr(text) {
  58. /*
  59. Checks if the string matches the format "number-number" with the second number larger.
  60.  
  61. Args:
  62. text: The string to check.
  63.  
  64. Returns:
  65. Two numbers if the string matches the format and the second number is larger, False otherwise.
  66. */
  67. const pattern = /^\d+-\d+$/; // Matches "number-number" format
  68. const match = pattern.exec(text);
  69.  
  70. if (match) {
  71. const [num1, num2] = match[0].split("-"); // Split into two numbers
  72. if (parseInt(num1) < parseInt(num2)){ // Check if second number is larger
  73. return [parseInt(num1), parseInt(num2)]
  74. }
  75. }
  76.  
  77. return false;
  78. }
  79. async function downloadChapter(id=null,filename=null, callback=(e)=>{}, ret = 3, skipCr = true){
  80. const dlBtn = document.querySelector("#dlz");
  81. if (!(dlBtn.classList.contains(btnDblTag))) {
  82. dlBtn.classList.add(btnDblTag);
  83. var s_p = 1;
  84. var e_p = gallery_.num_pages;
  85. let pt = prompt("Please enter the number of pages you want to download (Pattern: first page-last page. Example: 15-30)", "Click Continue to download all");
  86. const pg = parseStr(pt)
  87. if (pg) {
  88. s_p = pg[0];
  89. e_p = pg[1];
  90. }
  91. if (pt != null) {
  92. const zipFileWriter = new zip.BlobWriter();
  93. const zipWriter = new zip.ZipWriter(zipFileWriter);
  94. for (let i = s_p; i <= e_p; i++) {
  95. const fname = i.toString() + "." + ext;
  96. const src = media_url_ + id + "/"+ fname;
  97. console.log(((e_p-s_p)-(e_p-i)+1).toString() + "/" + (e_p-s_p).toString() + ": downloading "+src); //debug
  98. var blob;
  99. do {
  100. blob = await waitForXHRRequest(src,'GET','blob');
  101. ret -= 1;
  102. if (!blob && !ret) {
  103. alert("Error while downloading file: " + src)
  104. }
  105. else
  106. {
  107. await sleeper(200);
  108. }
  109. }
  110. while (!blob && ret);
  111. if (blob.type.includes("image")){
  112. const blobReader = new zip.BlobReader(blob);
  113. await zipWriter.add(fname, blobReader);
  114. }
  115. }
  116. await zipWriter.close();
  117. const zipFileBlob = await zipFileWriter.getData();
  118. const anchor = document.createElement("a");
  119. const clickEvent = new MouseEvent("click");
  120. anchor.href = window.URL.createObjectURL(zipFileBlob);
  121. anchor.download = filename;
  122. anchor.dispatchEvent(clickEvent);
  123. }
  124. }
  125. callback(dlBtn);
  126. }
  127. function setupButton(jNode) {
  128. const btnDiv = jNode.parentElement;
  129. const clonedElement = jNode.cloneNode(true); // Clone the element with all its content
  130. clonedElement.id = "dlz";
  131. if (xxx210) {
  132. clonedElement.style.marginLeft = "10px";
  133. }
  134. else{
  135. clonedElement.classList.remove(btnDblTag);
  136. clonedElement.querySelector(".top").textContent = "Click to download as zip";
  137. }
  138.  
  139. btnDiv.appendChild(clonedElement); // Append the cloned element to the last child
  140.  
  141. clonedElement.addEventListener("click", (e)=>{
  142. downloadChapter(gallery_.media_id, gallery_.title.japanese + ".zip", (el)=>{ el.classList.remove(btnDblTag); });
  143. });
  144. }
  145. var xxx210 = (location.host.split(".").at(-1) == "xxx") ? true : false;
  146. var gallery_, media_url_, ext, btnDblTag;
  147. document.addEventListener("DOMContentLoaded", async (e)=>{
  148. const srcUrl = document.querySelector("img[src*='cover'").src;
  149. media_url_ = "https://" + new URL(srcUrl).host;
  150. ext = srcUrl.split(".").at(-1);
  151. if (xxx210){
  152. gallery_ = {
  153. "media_id": document.querySelector("#load_id").value,
  154. "media_server": document.querySelector("#load_server").value,
  155. "media_dir": document.querySelector("#load_dir").value,
  156. "title": {
  157. "japanese": document.querySelector("div.info > h2").innerText,
  158. },
  159. "num_pages": parseInt(document.querySelector("#load_pages").value)
  160. };
  161. media_url_ += "/" + gallery_.media_dir + "/";
  162. btnDblTag = "disabled";
  163. }
  164. else{
  165. gallery_ = gallery;
  166. media_url_ += "/galleries/";
  167. btnDblTag = "btn-disabled";
  168. }
  169. var xhr = await waitForXHRRequest("https://raw.githubusercontent.com/gildas-lormeau/zip.js/v2.7.17/dist/zip.js","GET",undefined);
  170. eval(xhr);
  171. const btn = await waitForElm("i[class*='fa-download']");
  172. setupButton(btn[0].parentElement);
  173. });