刺猬猫章节自动下载

打开刺猬猫章节页面时自动保存文章到本地, 支持付费章节。

  1. // ==UserScript==
  2. // @name 刺猬猫章节自动下载
  3. // @namespace https://github.com/NateScarlet/Scripts/tree/master/user-script
  4. // @description 打开刺猬猫章节页面时自动保存文章到本地, 支持付费章节。
  5. // @grant none
  6. // @include https://www.ciweimao.com/chapter/*
  7. // @run-at document-idle
  8. // @version 2023.12.05+fbf1a78c
  9. // ==/UserScript==
  10.  
  11. "use strict";
  12. (() => {
  13. var __async = (__this, __arguments, generator) => {
  14. return new Promise((resolve, reject) => {
  15. var fulfilled = (value) => {
  16. try {
  17. step(generator.next(value));
  18. } catch (e) {
  19. reject(e);
  20. }
  21. };
  22. var rejected = (value) => {
  23. try {
  24. step(generator.throw(value));
  25. } catch (e) {
  26. reject(e);
  27. }
  28. };
  29. var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
  30. step((generator = generator.apply(__this, __arguments)).next());
  31. });
  32. };
  33.  
  34. // src/utils/elementRootText.ts
  35. function elementRootText(element) {
  36. let ret = "";
  37. for (const i of element.childNodes) {
  38. if (i.nodeType === i.TEXT_NODE) {
  39. ret += i.nodeValue;
  40. }
  41. }
  42. return ret.trim();
  43. }
  44.  
  45. // src/utils/canvasToMarkdown.ts
  46. function canvasToMarkdown(canvas, alt = "", title = "") {
  47. return `![${alt}](${canvas.toDataURL()} "${title}")`;
  48. }
  49.  
  50. // src/utils/isCanvasTainted.ts
  51. function isCanvasTainted(canvas) {
  52. try {
  53. canvas.getContext("2d").getImageData(0, 0, 1, 1);
  54. return false;
  55. } catch (err) {
  56. return err instanceof DOMException && err.name === "SecurityError";
  57. }
  58. }
  59.  
  60. // src/utils/imageToCanvas.ts
  61. function imageToCanvas(_0) {
  62. return __async(this, arguments, function* (img, {
  63. background
  64. } = {}) {
  65. const canvas = document.createElement("canvas");
  66. canvas.width = img.naturalWidth;
  67. canvas.height = img.naturalHeight;
  68. const ctx = canvas.getContext("2d");
  69. if (background) {
  70. ctx.fillStyle = background;
  71. ctx.fillRect(0, 0, canvas.width, canvas.height);
  72. }
  73. ctx.drawImage(img, 0, 0);
  74. if (img.src && img.crossOrigin !== "anonymous" && isCanvasTainted(canvas)) {
  75. const corsImage = new Image();
  76. corsImage.crossOrigin = "anonymous";
  77. corsImage.src = img.src;
  78. yield corsImage.decode();
  79. return imageToCanvas(corsImage, { background });
  80. }
  81. return canvas;
  82. });
  83. }
  84.  
  85. // src/utils/imageToMarkdown.ts
  86. function imageToMarkdown(_0) {
  87. return __async(this, arguments, function* (img, {
  88. background
  89. } = {}) {
  90. return canvasToMarkdown(
  91. yield imageToCanvas(img, { background }),
  92. img.alt,
  93. img.title
  94. );
  95. });
  96. }
  97.  
  98. // src/utils/loadImage.ts
  99. function loadImage(url) {
  100. return __async(this, null, function* () {
  101. const img = new Image();
  102. img.src = url;
  103. img.alt = url;
  104. yield img.decode();
  105. return img;
  106. });
  107. }
  108.  
  109. // src/utils/sleep.ts
  110. function sleep(duration) {
  111. return __async(this, null, function* () {
  112. return new Promise((resolve) => {
  113. setTimeout(resolve, duration);
  114. });
  115. });
  116. }
  117.  
  118. // src/ciweimao.com/download.user.ts
  119. var __name__ = "刺猬猫章节自动下载";
  120. (function() {
  121. return __async(this, null, function* () {
  122. const chapter = document.querySelector("#J_BookCnt .chapter").firstChild.textContent;
  123. let lines = [];
  124. let startTime = Date.now();
  125. while (lines.length === 0 && Date.now() - startTime < 6e4) {
  126. yield sleep(1e3);
  127. for (const i of document.querySelectorAll("#J_BookImage")) {
  128. const url = i.style["background-image"].match(
  129. /(?:url\(")?(.+)(?:"\))?/
  130. )[1];
  131. const line = yield imageToMarkdown(yield loadImage(url));
  132. lines.push(line);
  133. }
  134. for (const i of document.querySelectorAll("#J_BookRead p.chapter")) {
  135. const line = elementRootText(i);
  136. lines.push(line);
  137. for (const img of i.querySelectorAll("img")) {
  138. yield img.decode();
  139. lines.push(yield imageToMarkdown(img));
  140. }
  141. }
  142. for (const i of document.querySelectorAll("p.author_say")) {
  143. const line = elementRootText(i);
  144. lines.push(` ${line}`);
  145. for (const img of i.querySelectorAll("img")) {
  146. yield img.decode();
  147. lines.push(yield imageToMarkdown(img));
  148. }
  149. }
  150. lines = lines.filter((i) => i.length > 0);
  151. }
  152. console.log(`${__name__}: 获取到 ${lines.length} 行`);
  153. const file = new Blob([`# ${chapter}
  154.  
  155. `, lines.join("\n\n") + "\n"], {
  156. type: "text/markdown"
  157. });
  158. const anchor = document.createElement("a");
  159. anchor.href = URL.createObjectURL(file);
  160. anchor.download = `${location.pathname.split("/").slice(-1)[0]} ${document.title}.md`;
  161. anchor.style["display"] = "none";
  162. document.body.append(anchor);
  163. anchor.click();
  164. setTimeout(() => {
  165. document.body.removeChild(anchor);
  166. URL.revokeObjectURL(anchor.href);
  167. }, 0);
  168. });
  169. })();
  170. })();