刺猬猫小说下载

刺猬猫小说下载,全本下载,单章下载,暂不支持付费章节

  1. // ==UserScript==
  2. // @name 刺猬猫小说下载
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.55
  5. // @description 刺猬猫小说下载,全本下载,单章下载,暂不支持付费章节
  6. // @author backrock12
  7. // @match *://www.ciweimao.com/chapter-list/*
  8. // @match *://www.ciweimao.com/chapter/*
  9. // @match *://www.ciweimao.com/book/*
  10. // @require https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js
  11. // @require https://cdn.jsdelivr.net/npm/file-saver@1.3.8/FileSaver.min.js
  12. // @grant GM_xmlhttpRequest
  13.  
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. var DivInited = false, IsCancel = false;
  20. var WCContent, WCWords, WCQuit, WCSave, WCContinue;
  21. var title, info;
  22. var downlist = [];
  23. var infolist = [];
  24. var lhref;
  25. var vol = 1;
  26. var num = 0;
  27. var isimg = false;
  28. var isvip = false;
  29.  
  30. function initDiv() {
  31. console.log("initDiv");
  32. if (DivInited) return;
  33. DivInited = true;
  34. var content = document.createElement("div");
  35. document.body.appendChild(content);
  36. content.outerHTML = `
  37. <div id="CWDownContent" style='display:none' >
  38. <div style="width:360px;height:100px;position:fixed;left:50%;top:50%;margin-top:-50px;margin-left:-180px;z-index:100000;background-color:#ffffff;border:1px solid #afb3b6;opacity:0.95;filter:alpha(opacity=95);box-shadow:5px 5px 20px 0px#000;">
  39. <div id="CWDownWords" style="font-size:12px;position:absolute;width:290px;height:90px;padding: 8px;border-radius: 10px;float: left;">
  40. </div>
  41. <div style="float: right;">
  42. <div id="CWDownSave" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 5px 5px 3px 3px;">
  43. <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">保存</span>
  44. </div>
  45. <div id="CWDownQuit" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 3px;">
  46. <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">取消</span>
  47. </div>
  48. <div id="CWCContinue" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 3px;">
  49. <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">繼續</span>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. `;
  55.  
  56. WCContent = document.querySelector("#CWDownContent");
  57. WCWords = document.querySelector("#CWDownWords");
  58. WCQuit = document.querySelector("#CWDownQuit");
  59. WCSave = document.querySelector("#CWDownSave");
  60. WCContinue = document.querySelector("#CWCContinue");
  61.  
  62. WCQuit.onclick = function () {
  63. IsCancel = true;
  64. DivInited = false;
  65. WCContent.style.display = "none";
  66. WCWords.innerHTML = '';
  67. WCContent.parentNode.removeChild(WCContent);
  68. };
  69. WCContinue.onclick = function () {
  70. if (downlist.length == 0) {
  71. Analysis();
  72. }
  73. else {
  74. continueDown();
  75. }
  76. };
  77. WCSave.onclick = function () {
  78. SaveText();
  79. };
  80. }
  81.  
  82. function ShowWords(value) {
  83. WCWords.innerHTML = (title ? title + '<br>' : '') + value;
  84. }
  85.  
  86.  
  87. class Book {
  88. constructor(name, url, text, title_mk, lock, complete, status, iframeId) {
  89. this.name = name;
  90. this.url = url;
  91. this.text = text;
  92. this.title_mk = title_mk;
  93. this.lock = lock;
  94. this.complete = complete || false;
  95. this.status = status || 'Null';
  96. this.iframeId = iframeId
  97. this.num = num++
  98. }
  99.  
  100. setstatus(status, text) {
  101. this.complete = true;
  102. this.status = status;
  103. if (text)
  104. this.text = text;
  105. }
  106. setiframeId(iframeId) {
  107. this.complete = false;
  108. this.status = 'Down';
  109. this.iframeId = iframeId;
  110. }
  111. setnull() {
  112. this.complete = false;
  113. this.status = 'Null';
  114. this.iframeId = '';
  115. }
  116. isDown() {
  117. return this.status == 'Down';
  118. }
  119. isEnd() {
  120. // return this.title_mk == false && this.complete && (this.iframeId || this.iframeId != '');
  121. return this.title_mk == false && this.complete;
  122. }
  123. isCan() {
  124. // return this.title_mk == false && this.complete == false && (!this.iframeId || this.iframeId == '');
  125. return this.title_mk == false && this.complete == false;
  126. }
  127.  
  128. }
  129.  
  130. function* cratebookmap(items) {
  131. for (let i of items) {
  132. if (i.isCan()) {
  133. yield i;
  134. }
  135. }
  136. }
  137.  
  138. async function loopDown() {
  139. console.log('loopDown');
  140. let bookobj = cratebookmap(downlist);
  141. let result = bookobj.next();
  142. while (!result.done && !IsCancel) {
  143. if (bookobj)
  144. ShowWords(`共 ${downlist.length} 章节<br>已下载完成 ${result.value.num - 1} 章节,剩余 ${downlist.length - result.value.num} 章节<br>正在下载 ${result.value.num}`);
  145. if (!isvip && result.value.lock) {
  146. result.value.setstatus('OK', '\r\n' + result.value.name + '\r\n' + '付费章节暂时无法下载');
  147. console.log(result.value.num + ' vip not download');
  148. } else {
  149. await IframeInit(result.value);
  150. IframeClear(result.value);
  151. }
  152. result = bookobj.next();
  153. }
  154. if (result.done) {
  155. SaveText();
  156. //getdesc(SaveText)
  157. }
  158. }
  159.  
  160. function continueDown() {
  161. console.log('continueDown');
  162. IsCancel = true;
  163. downlist.forEach((item) => {
  164. if (item.isDown()) {
  165. IframeClear(item);
  166. item.setnull();
  167. }
  168. });
  169. IsCancel = false;
  170. loopDown();
  171. }
  172.  
  173. function IframeClear(bookobj) {
  174. console.log('IframeClear' + bookobj.iframeId);
  175. var ele = document.getElementById(bookobj.iframeId);
  176.  
  177. try {
  178. ele.src = 'about:blank';
  179. ele.onload = null;
  180. ele.contentWindow.document.write('');
  181. ele.contentWindow.document.clear();
  182. ele.contentWindow.close();
  183. ele.parentNode.removeChild(ele);
  184. ele = null;
  185. } catch (e) {
  186. console.log('IframeClear' + e.message);
  187. }
  188. bookobj.iframeId = '';
  189. }
  190.  
  191. async function IframeInit(bookobj) {
  192.  
  193. return new Promise((resolve, reject) => {
  194. var iframeId = 'iframe_' + vol + '_' + bookobj.num;
  195. console.log('IframeInit' + iframeId);
  196.  
  197. var ele1 = document.createElement('iframe');
  198. ele1.src = bookobj.url + '#Autodown';
  199. ele1.name = iframeId;
  200. ele1.id = iframeId;
  201. ele1.width = "195px";
  202. ele1.height = "126px";
  203. ele1.style.display = 'none';
  204.  
  205. ele1.onload = function () {
  206. var frame = this;//document.getElementById(iframeId);
  207. if (frame) {
  208. resolve(downtext(frame.contentDocument, bookobj));
  209. }
  210. }
  211. document.body.appendChild(ele1);
  212. bookobj.setiframeId(iframeId);
  213.  
  214. });
  215. }
  216.  
  217.  
  218. async function getRequest() {
  219. let id = /www\.ciweimao\.com\/chapter-list\/(\d{5,})/.exec(location.href);
  220. if (!id) {
  221. throw "id is null";
  222. }
  223. let url = 'https://www.ciweimao.com/book/' + id[1]
  224. console.log(url);
  225. return new Promise(function (resolve, reject) {
  226. GM_xmlhttpRequest({
  227. method: 'GET',
  228. url: url,
  229. timeout: 3000,
  230. onerror: function (result) {
  231. console.log('onerror');
  232. console.log(result);
  233. },
  234. onload: function (result) {
  235. resolve(result.response);
  236. }
  237. });
  238. });
  239. }
  240.  
  241.  
  242.  
  243. function getdesc(fun) {
  244. try {
  245. console.log("getdesc");
  246.  
  247. let id = /www\.ciweimao\.com\/chapter-list\/(\d{5,})\/book_detail/.exec(lhref);
  248. let url = 'https://www.ciweimao.com/book/' + id[1]
  249. console.log(url);
  250. GM_xmlhttpRequest({
  251. method: 'GET',
  252. url: url,
  253. timeout: 3000,
  254. onerror: function (result) {
  255. console.log('onerror');
  256. console.log(result);
  257. },
  258. onload: function (result) {
  259. var str = result.response;
  260. var doc = document.implementation.createHTMLDocument('');
  261. doc.documentElement.innerHTML = str;
  262. let desc = doc.querySelector(".book-desc").innerText;
  263. let grade = doc.querySelector(".book-grade").innerText;
  264. let update = doc.querySelector(".update-time").innerText;
  265. let author = doc.querySelector("div.book-info h3.title span").innerText;
  266. let title = doc.querySelector("div.book-info h3.title").innerText;
  267. title = title.replace(author, '');
  268.  
  269. infolist.push(title);
  270. infolist.push(author);
  271. infolist.push(grade);
  272. infolist.push(update);
  273. infolist.push(desc.trim());
  274. console.log(infolist);
  275. fun();
  276. return true;
  277. }
  278. });
  279. }
  280. catch (e) {
  281. return false;
  282. }
  283. }
  284.  
  285.  
  286.  
  287. async function getdescasync() {
  288. try {
  289. console.log("getdesc");
  290.  
  291. var str = await getRequest();
  292. var doc = document.implementation.createHTMLDocument('');
  293. doc.documentElement.innerHTML = str;
  294. let desc = doc.querySelector(".book-desc").innerText;
  295. let grade = doc.querySelector(".book-grade").innerText;
  296. let update = doc.querySelector(".update-time").innerText;
  297. let author = doc.querySelector("div.book-info h3.title span").innerText;
  298. let title = doc.querySelector("div.book-info h3.title").innerText;
  299. title = title.replace(author, '');
  300.  
  301. infolist.push(title);
  302. infolist.push(author);
  303. infolist.push(grade);
  304. infolist.push(update);
  305. infolist.push(desc.trim());
  306. console.log(infolist);
  307. return true;
  308. }
  309. catch (e) {
  310. return false;
  311. }
  312. }
  313.  
  314.  
  315. function Analysis() {
  316. console.log("Analysis");
  317. IsCancel = false;
  318. title = '';
  319. info = '';
  320. downlist = [];
  321. infolist = [];
  322.  
  323. initDiv();
  324. if (WCContent) {
  325. WCContent.style.display = "block";
  326. ShowWords(`分析网页中`);
  327. }
  328.  
  329. if (!getdescasync()) {
  330. ShowWords(`获取文章信息失败`);
  331. return;
  332. }
  333.  
  334. title = document.querySelector(".hd h3").innerText;
  335.  
  336. for (const i of document.querySelectorAll(".hd p")) {
  337. info += "\r\n" + i.innerText;
  338. }
  339. console.log(title);
  340.  
  341. for (const c of document.querySelectorAll(".book-chapter .book-chapter-box")) {
  342. var ctitle = c.querySelector('.sub-tit').innerText;
  343. downlist.push(
  344. new Book(ctitle, '', ctitle, true, false, true, 'OK')
  345. );
  346. for (const a of c.querySelectorAll('.book-chapter-list li a')) {
  347. if (a.querySelector('.icon-lock,.icon-unlock'))
  348. var lock = true
  349. else
  350. lock = false;
  351. downlist.push(
  352. new Book(a.innerText, a.href, '', false, lock)
  353. );
  354. };
  355. };
  356.  
  357. console.log(downlist);
  358.  
  359. if (downlist.length == 0) {
  360. ShowWords(`分析网页失败`);
  361. return;
  362. }
  363. loopDown();
  364. }
  365.  
  366.  
  367. function SaveText() {
  368. var texts = [];
  369. WCContent.style.display = "block";
  370. if (downlist.length > 0 && infolist.length == 0)
  371. getdesc();
  372.  
  373. var ok = 0, error = 0;
  374. downlist.forEach(function (c) {
  375. if (c.isEnd())
  376. ok++
  377. else if (c.title_mk && c.complete)
  378. error++;
  379. texts.push(c.text);
  380. });
  381. ShowWords(`已下载完成<br>共 ${downlist.length} 章节<br>成功 ${ok} 章节,失败 ${error} 章节 `);
  382.  
  383.  
  384. var blob = new Blob([infolist.join("\r\n\r\n"), "\r\n\r\n", texts.join("\r\n")], { type: "text/plain;charset=utf-8" });
  385. saveAs(blob, `${title}.txt`);
  386. }
  387.  
  388. function getElementRootText(element) {
  389. let ret = "";
  390. for (const i of element.childNodes) {
  391. if (i.nodeType === i.TEXT_NODE) {
  392. ret += i.nodeValue;
  393. }
  394. }
  395. return ret.replace(/^\s+|\s+$/g, "");
  396. }
  397.  
  398. function image2line(img) {
  399.  
  400. var dataURL = GM_xmlhttpRequest({
  401. method: 'GET',
  402. url: img.src,
  403. responseType: "blob",
  404. onload: function (result) {
  405. var reader = new window.FileReader();
  406. reader.readAsDataURL(result.response);
  407. reader.onloadend = function () {
  408. return reader.result;
  409. }
  410. }
  411. });
  412.  
  413. return `![${img.alt}](${dataURL} "${img.title}")`;
  414.  
  415. return;
  416. img.crossOrigin = 'Anonymous'
  417. html2canvas(img, {
  418. allowTaint: true,
  419. logging: false,
  420. useCORS: true,
  421. }
  422. ).then(function (canvas) {
  423. var dataUrl = canvas.toDataURL();
  424. console.log(dataUrl);
  425. console.log(canvas);
  426.  
  427. var im = document.createElement("img");
  428. im.src = dataUrl;
  429.  
  430. document.body.append(canvas);
  431.  
  432. });
  433.  
  434.  
  435. return;
  436. img.crossOrigin = "Anonymous";
  437. var canvas = document.createElement("canvas");
  438. canvas.width = img.naturalWidth;
  439. canvas.height = img.naturalHeight;
  440. var ctx = canvas.getContext("2d");
  441. ctx.drawImage(img, 0, 0, img.width, img.height);
  442. var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
  443. console.log('img4');
  444. var dataURL = canvas.toDataURL("image/" + ext);
  445. console.log('img5');
  446. console.log(dataURL);
  447.  
  448. return `![${img.alt}](${dataURL} "${img.title}")`;
  449. }
  450.  
  451. async function getimagedata(img) {
  452. return new Promise((resolve, reject) => {
  453. GM_xmlhttpRequest({
  454. method: 'GET',
  455. url: img.src,
  456. responseType: "blob",
  457. onload: function (result) {
  458. var reader = new window.FileReader();
  459. reader.readAsDataURL(result.response);
  460. reader.onloadend = function () {
  461. resolve(`![${img.alt}](${reader.result} "${img.title}")`);
  462. }
  463. }
  464. });
  465. });
  466. }
  467.  
  468.  
  469. async function image2lineasync(img) {
  470. return new Promise((resolve, reject) => {
  471. resolve(image2line(img));
  472. });
  473. }
  474.  
  475. async function imageUrl2line(url) {
  476. return new Promise((resolve, reject) => {
  477. const img = new Image();
  478. img.onload = () => {
  479. resolve(image2line(img));
  480. };
  481. img.src = url;
  482. console.log(url);
  483. });
  484. }
  485.  
  486. async function downtext(str, bookobj) {
  487.  
  488. console.log('downtext');
  489.  
  490. if (bookobj && bookobj.lock) {
  491. bookobj.setstatus('OK', '\r\n' + bookobj.name + '\r\n' + '付费章节暂时无法下载');
  492. return;
  493. }
  494.  
  495.  
  496. try {
  497.  
  498. var doc = str;
  499. let time, num;
  500. let lines = [];
  501.  
  502. let title = doc.querySelector("#J_BookCnt h3.chapter").firstChild.textContent;
  503.  
  504. for (const i of doc.querySelectorAll("#J_BookCnt p span")) {
  505. if (i.textContent.indexOf("更新时间")) {
  506. time = i.textContent;
  507. } else if (i.textContent.indexOf("字数")) {
  508. num = i.textContent;
  509. }
  510. }
  511.  
  512. lines.push('\r\n\r\n');
  513. lines.push(`# ${title}`);
  514. lines.push(` ${time}`);
  515. lines.push(` ${num}\r\n`);
  516.  
  517. if ((bookobj && bookobj.lock) || doc.querySelectorAll("#J_BookImage").length > 0) {
  518. lines.push(`付费章节暂时无法下载`);
  519. } else {
  520. // 收费章节
  521. if (isvip)
  522. for (const i of doc.querySelectorAll("#J_BookImage")) {
  523. const url = i.style["background-image"].match(/(?:url\(")?(.+)(?:"\))?/)[1];
  524. const line = await imageUrl2line(url);
  525. lines.push(line);
  526. }
  527.  
  528. // 免费章节
  529. for (const i of doc.querySelectorAll("#J_BookRead p:not(.author_say)")) {
  530. let line = getElementRootText(i);
  531. lines.push(line);
  532. if (isimg)
  533. for (const img of i.querySelectorAll("img")) {
  534. const line = await getimagedata(img);
  535. lines.push(line);
  536. }
  537. }
  538.  
  539. // 作者说
  540. for (const i of doc.querySelectorAll("p.author_say")) {
  541. let line = getElementRootText(i);
  542. lines.push(` ${line}`);
  543. if (isimg)
  544. for (const img of i.querySelectorAll("img")) {
  545. const line = await getimagedata(img);
  546. lines.push(line);
  547. }
  548. }
  549.  
  550. var blob = new Blob([lines.join("\r\n")], { type: "text/plain;charset=utf-8" });
  551.  
  552. if (bookobj) {
  553. bookobj.setstatus('OK', lines.join("\r\n"));
  554. } else {
  555. IsCancel = false;
  556. saveAs(blob, title + ".txt");
  557. }
  558. return true;
  559. }
  560. }
  561. catch (e) {
  562. if (bookobj) {
  563. bookobj.setstatus('ERROR ', bookobj.name + " 下载错误!");
  564. ShowWords(bookobj.text);
  565. }
  566. console.log('downtext ERROR ' + e.message);
  567. return false;
  568. }
  569. }
  570.  
  571.  
  572.  
  573.  
  574.  
  575. function Inited() {
  576. if (location.hash && location.hash == '#Autodown') {
  577. console.log('Autodown');
  578. } else {
  579. if (location.hash && location.hash == '#Autoclick') {
  580. var autoclick = true;
  581. }
  582.  
  583. console.log('Inited');
  584. let reg = new RegExp('www.ciweimao.com/book/*');
  585. if (reg.test(location.href)) {
  586. var b = document.querySelector(".btn-group");
  587. if (b) {
  588. let id = location.href.substr(location.href.lastIndexOf('/') + 1);
  589. let e = document.createElement('a');
  590. e.id = 'BDownBtn';
  591. e.textContent = '全本下载';
  592. e.className = 'btn btn-lg btn-danger'
  593. e.target = '_blank'
  594. e.href = `https://www.ciweimao.com/chapter-list/${id}/book_detail#Autoclick`;
  595. b.append(e);
  596. }
  597. }
  598.  
  599. let t = document.querySelector(".hd");
  600. if (t) {
  601. let e = document.createElement('button');
  602. e.id = 'TDownBtn';
  603. e.textContent = '全本下载';
  604. e.className = 'btn btn-md btn-default'
  605. e.onclick = Analysis;
  606. t.append(e);
  607. if (autoclick)
  608. Analysis();
  609. }
  610.  
  611. let ct = document.querySelector(".read-hd");
  612. if (ct) {
  613. let ce = document.createElement('button');
  614. ce.id = 'CDownBtn';
  615. ce.textContent = '单章下载';
  616. ce.className = 'btn btn-md btn-default'
  617. ce.onclick = function () { downtext(document); };
  618. ct.append(ce);
  619. }
  620. }
  621. }
  622. Inited();
  623. //Analysis();
  624.  
  625. })();

QingJ © 2025

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