笔趣阁下载器

可在笔趣阁下载小说(TXT格式),在小说目录页面使用。(仅供交流,可能存在bug)(已测试网址:beqege.cc|bigee.cc|bqgui.cc|bbiquge.la|3bqg.cc|xbiqugew.com)

  1. // ==UserScript==
  2. // @name 笔趣阁下载器
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.8
  5. // @description 可在笔趣阁下载小说(TXT格式),在小说目录页面使用。(仅供交流,可能存在bug)(已测试网址:beqege.cc|bigee.cc|bqgui.cc|bbiquge.la|3bqg.cc|xbiqugew.com)
  6. // @author Yearly
  7. // @match https://www.beqege.cc/*/
  8. // @match https://www.bigee.cc/book/*/
  9. // @match https://www.bqgui.cc/book/*/
  10. // @match https://www.3bqg.cc/book/*/
  11. // @match https://www.bbiquge.la/*/
  12. // @match https://www.xbiqugew.com/book/*/
  13. // @match https://www.beqege.com/*/
  14. // @match https://www.bqg78.cc/book/*/
  15. // @match https://www.biquge.tw/book/*/
  16. // @license GPL-3.0
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_addStyle
  19. // @namespace https://gf.qytechs.cn/scripts/500170
  20. // @supportURL https://gf.qytechs.cn/scripts/500170
  21. // @homepageURL https://gf.qytechs.cn/scripts/500170
  22. // @icon https://www.beqege.cc/favicon.ico
  23. // ==/UserScript==
  24.  
  25. (function() {
  26.  
  27. GM_addStyle(`
  28. #fetchContentModal {
  29. display: block;
  30. border-radius: 10px;
  31. position: fixed;
  32. top: 40%;
  33. left: 50%;
  34. transform: translate(-50%, -50%);
  35. background: white;
  36. padding: 5px 20px 10px 20px;
  37. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  38. z-index: 10000;
  39. width: 500px;
  40. text-align: center;
  41. }
  42. #fetchContentModal h3{
  43. margin: 1.3em;
  44. }
  45. #fetchContentModal label{
  46. display: block;
  47. font-size: 14px;
  48. }
  49. #fetchContentModal input[type="number"] {
  50. width: auto;
  51. margin: 2px 3px;
  52. text-align: center;
  53. -moz-appearance: textfield;
  54. appearance: textfield;
  55. }
  56. input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button {
  57. -webkit-appearance: inner-spin-button;
  58. opacity: 1;
  59. }
  60. #fetchContentModal button {
  61. width: 100%;
  62. margin: 10px 0;
  63. }
  64. #fetchContentModal #fetchContentProgress {
  65. width: 100%;
  66. background: #f3f3f3;
  67. border: 1px solid #ccc;
  68. margin: 10px 0;
  69. }
  70. #fetchContentProgress div {
  71. width: 0;
  72. height: 20px;
  73. background: #4caf50;
  74. text-align: center;
  75. margin-left: 0;
  76. color: #960;
  77. white-space: nowrap;
  78. }
  79. #fetchContentModal td {
  80. white-space: nowrap;
  81. overflow: hidden;
  82. }
  83. `);
  84.  
  85. const modalHtml = `
  86. <div id="fetchContentModal" style="display:none;">
  87. <h3>小说下载工具<span id="fetcModalClose" style="cursor: pointer; float: right; margin:-8px -4px;">✕</span></h3>
  88. <label id="_book_info"></label>
  89. <label for="ranges">下载章节范围:</label>
  90. <table style="width:100%; margin-bottom:10px; table-layout:fixed;">
  91. <tbody>
  92. <colgroup>
  93. <col style="width: 45%;">
  94. <col style="width: 10%;">
  95. <col style="width: 45%;">
  96. </colgroup>
  97. <tr>
  98. <th style="width:45%; text-align:right;"><input type="number" id="_startRange" min="1" value="1"></th>
  99. <th style="width:10%; text-align:center;"> ~ </th>
  100. <th style="width:45%; text-align: left;"><input type="number" id="_finalRange" min="1" value="2"></th>
  101. </tr>
  102. <tr>
  103. <td style="width:45%; text-align:right;" id="_startRange_title"></td>
  104. <td style="width:10%; text-align:center;"> ~ </td>
  105. <td style="width:45%; text-align:left;" id="_finalRange_title"></td>
  106. </tr>
  107. </tbody>
  108. </table>
  109. <label id="_warn_info"></label>
  110. <label id="_warn_info"></label>
  111. <button id="fetchContentButton">开始下载</button>
  112. <div id="fetchContentProgress">
  113. <div></div>
  114. </div>
  115. <a id="_downlink"></a>
  116. </div>
  117. `;
  118. document.body.insertAdjacentHTML('beforeend', modalHtml);
  119.  
  120. // 获取元素
  121. const modal = document.getElementById('fetchContentModal');
  122. const startRangeInput = document.getElementById('_startRange');
  123. const finalRangeInput = document.getElementById('_finalRange');
  124.  
  125. const startTitle = document.getElementById('_startRange_title');
  126. const finalTitle = document.getElementById('_finalRange_title');
  127.  
  128. const fetchButton = document.getElementById('fetchContentButton');
  129. const progressBar = document.getElementById('fetchContentProgress').firstElementChild;
  130. const downlink = document.getElementById('_downlink');
  131. const warnInfo = document.getElementById('_warn_info');
  132. const bookInfo = document.getElementById('_book_info');
  133. const fetcClose = document.getElementById('fetcModalClose');
  134.  
  135. let booktitle = null;
  136. let tocDiv = null;
  137. let chapters = null;
  138.  
  139. let startIndex, finalIndex;
  140.  
  141. function downloadMenu() {
  142. modal.style.display = 'block';
  143. tocDiv = document.querySelector("#list") || document.querySelector(".listmain") || document.querySelector(".list-chapter");
  144.  
  145. if(tocDiv.querySelector('dl center.clear')) {
  146. chapters = document.querySelectorAll("dl center.clear ~ dd > a[href]")
  147. } else {
  148. chapters = tocDiv.querySelectorAll("dl dd > a[href]")
  149. }
  150. if(!chapters.length) {
  151. chapters = document.querySelectorAll("div.list-chapter > div.booklist > ul > li > a[href]")
  152. }
  153.  
  154. startRangeInput.max = chapters.length;
  155. finalRangeInput.max = chapters.length;
  156.  
  157. startIndex = 0;
  158. finalIndex = chapters.length - 1;
  159.  
  160. finalRangeInput.value = chapters.length;
  161. startTitle.innerText = chapters[startIndex].innerText;
  162. finalTitle.innerText = chapters[finalIndex].innerText;
  163.  
  164. const title = document.querySelector("#maininfo #info h1") || document.querySelector(".info h1") || document.querySelector("h1") ;
  165. if(title) booktitle = title.innerText;
  166. else booktitle = document.title;
  167. bookInfo.innerText=`当前小说:《${booktitle}》,共 ${chapters.length} 章。`
  168. warnInfo.innerText=`设置范围后点击开始下载,并稍作等待。\n若章节过多下载卡住,可尝试减小章节范围分次下载。`
  169.  
  170. if(document.querySelector('button#downloadMenuBtn')) { document.querySelector('button#downloadMenuBtn').hidden=true; }
  171. }
  172.  
  173. if (document.querySelector("h1")) {
  174. let downloadMenuBtn = document.createElement("button");
  175. downloadMenuBtn.innerText = "下载"
  176. downloadMenuBtn.id="downloadMenuBtn"
  177. downloadMenuBtn.style="padding:2px 10px; margin:auto 10px; font-size:15px; background:#ccF8;"
  178. document.querySelector("h1").append(downloadMenuBtn)
  179. downloadMenuBtn.addEventListener('click', downloadMenu);
  180. }
  181.  
  182. GM_registerMenuCommand('小说下载工具', downloadMenu);
  183.  
  184. fetcClose.addEventListener('click', async () => {
  185. modal.style.display = 'none';
  186. if(document.querySelector('button#downloadMenuBtn')) { document.querySelector('button#downloadMenuBtn').hidden=false; }
  187. });
  188.  
  189. startRangeInput.addEventListener('input', function() {
  190. let val = parseInt(startRangeInput.value)
  191. if (!isNaN(val)) {
  192. if (val < 1) {val = 1;}
  193. if (val > chapters.length) {val = chapters.length;}
  194. startRangeInput.value = val;
  195. startIndex = parseInt(val) - 1;
  196. startTitle.innerText = chapters[startIndex].innerText;
  197. }
  198. });
  199.  
  200. finalRangeInput.addEventListener('input', function() {
  201. let val = parseInt(finalRangeInput.value)
  202. if (!isNaN(val)) {
  203. if (val < 1) {val = 1;}
  204. if (val > chapters.length) {val = chapters.length;}
  205. finalRangeInput.value = val;
  206. finalIndex = parseInt(val) - 1;
  207. finalTitle.innerText = chapters[finalIndex].innerText;
  208. }
  209. });
  210.  
  211. fetchButton.addEventListener('click', async () => {
  212. downlink.innerText = "";
  213. downlink.href = null;
  214. downlink.download = null;
  215. fetchButton.disabled = true;
  216.  
  217. if (startIndex > finalIndex) {
  218. let temp0 = startIndex;
  219. let temp1 = finalIndex;
  220. startIndex = temp1;
  221. finalIndex = temp0;
  222. startRangeInput.value = startIndex+1;
  223. startTitle.innerText = chapters[startIndex].innerText;
  224. finalRangeInput.value = finalIndex+1;
  225. finalTitle.innerText = chapters[finalIndex].innerText;
  226. }
  227.  
  228. const links = document.querySelectorAll("dl dd > a[href], div.list-chapter > div.booklist > ul > li > a[href]")
  229.  
  230. const selectedLinks = Array.from(links).slice(startIndex, finalIndex+1);
  231.  
  232. if (!booktitle){
  233. booktitle = document.title;
  234. }
  235. const results = [];
  236. const totalLinks = selectedLinks.length;
  237. let completedRequests = 0;
  238.  
  239. const retry = [];
  240. const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  241. let queue = 0;
  242.  
  243.  
  244. async function fetchContent(url, link) {
  245. let allContent = '';
  246. let title = '';
  247.  
  248. async function fetchPage(pageUrl) {
  249. const response = await fetch(pageUrl, { method: "GET" });
  250. if (!response.ok) {
  251. throw new Error(`HTTP error! Status: ${response.status}`);
  252. }
  253.  
  254. let text = '';
  255. const contentType = response.headers.get('Content-Type');
  256. if (contentType) {
  257. let charset = 'utf-8'; // 默认编码
  258. const charsetMatch = contentType && contentType.match(/charset=([^;]+)/i);
  259. if (charsetMatch) {
  260. charset = charsetMatch[1].toLowerCase();
  261. }
  262. const arrayBuffer = await response.arrayBuffer();
  263. const decoder = new TextDecoder(charset);
  264. text = decoder.decode(new Uint8Array(arrayBuffer));
  265. } else {
  266. text = await response.text();
  267. }
  268.  
  269. const parser = new DOMParser();
  270. const doc = parser.parseFromString(text, 'text/html');
  271. const contentDiv = doc.querySelector('div#content') || doc.querySelector('#chaptercontent') || doc.querySelector('.content');
  272.  
  273. if (contentDiv) {
  274. contentDiv.querySelectorAll('div#device').forEach(function(web_ad) {
  275. web_ad.remove();
  276. });
  277. contentDiv.querySelectorAll('p.readinline > a[href*="javascript:"]').forEach(function(web_op) {
  278. web_op.remove();
  279. });
  280. contentDiv.innerHTML = contentDiv.innerHTML.replaceAll('<br>', '\n');
  281. allContent += contentDiv.innerText + '\n\n'; // 添加页面内容到总内容
  282. }
  283.  
  284. if (doc.querySelector('h1')) {
  285. title = doc.querySelector('h1').innerText;
  286. }
  287.  
  288. const nextPage = doc.querySelector('.read-page a[href][rel="next"]');
  289. if (nextPage && (nextPage.innerText == '下一页')) {
  290. console.log(nextPage.href);
  291. await fetchPage(new URL(nextPage.href, pageUrl).href);
  292. }
  293. }
  294.  
  295. await fetchPage(url);
  296.  
  297. if (link && !title) {
  298. title = link.innerText;
  299. }
  300.  
  301. return { title: title, content: allContent };
  302. }
  303.  
  304. const fetchAndParse = async (link, index) => {
  305. await delay(5 * index);
  306. await delay(5 * queue++);
  307.  
  308. const url = link.href;
  309. try {
  310. results[index] = await fetchContent(url, link);
  311. } catch (error) {
  312. results[index] = { title: link.innerText, content: `Error fetching ${url}: ${error}` };
  313. } finally {
  314. if (queue) queue--;
  315. // 更新进度条
  316. completedRequests++;
  317. const progress = Math.round((completedRequests / totalLinks) * 100);
  318. progressBar.style.width = `${progress}%`;
  319. progressBar.textContent = `${progress}% (${completedRequests}/${totalLinks})`;
  320. }
  321. };
  322.  
  323. Promise.all(selectedLinks.map((link, index) => fetchAndParse(link, index)))
  324. .then(() => {
  325. const bookInfoDiv = document.querySelector("#maininfo #info") || document.querySelector("div.book div.info") || document.querySelector("h1");
  326. let finalResults = booktitle;
  327. if (bookInfoDiv ) finalResults = bookInfoDiv.innerText;
  328.  
  329. finalResults += `\n\n下载章节索引范围:${startIndex+1} ~ ${finalIndex+1}\n`;
  330. finalResults += `\n来自链接:${document.URL}\n`
  331. finalResults += "\n-----------------------\n";
  332.  
  333. results.forEach((result) => {
  334. finalResults += `\n## ${result.title}\n\n`;
  335. finalResults += result.content + '\n';
  336. });
  337.  
  338. finalResults += "\n-----------------------\n";
  339.  
  340. const blob = new Blob([finalResults], { type: 'text/plain' });
  341. downlink.innerText = "若未开始自动下载,点击这里";
  342. downlink.href = URL.createObjectURL(blob);
  343. downlink.download = `${booktitle}_${startIndex+1}~${finalIndex+1}.txt`;
  344. downlink.click();
  345. fetchButton.disabled = false;
  346. })
  347. .catch((error) => {
  348. console.error('Error fetching links:', error);
  349. });
  350.  
  351. });
  352. })();

QingJ © 2025

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