Chapter Downloader

Download up to 200 chapters on truyen.tangthuvien.vn

目前为 2025-04-21 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Chapter Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2
  5. // @description Download up to 200 chapters on truyen.tangthuvien.vn
  6. // @author You
  7. // @match https://truyen.tangthuvien.vn/doc-truyen/*
  8. // @match https://truyen.tangthuvien.vn/doc-truyen/*/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const MAX_CHAPTERS = 200;
  16. let downloadedCount = 0;
  17.  
  18. const panel = document.createElement('div');
  19. panel.id = 'reader-panel';
  20. panel.style.cssText = `
  21. position: fixed;
  22. top: 10%;
  23. right: 10px;
  24. width: 300px;
  25. background-color: #f8f8f8;
  26. border: 1px solid #ccc;
  27. border-radius: 5px;
  28. padding: 10px;
  29. z-index: 9999;
  30. box-shadow: 0 0 10px rgba(0,0,0,0.2);
  31. font-family: Arial, sans-serif;
  32. `;
  33.  
  34. const textarea = document.createElement('textarea');
  35. textarea.style.cssText = `width: 100%; height: 300px; margin-bottom: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;`;
  36.  
  37. const startButton = document.createElement('button');
  38. startButton.textContent = 'Bắt đầu';
  39. startButton.style.cssText = `width: 100%; padding: 8px; margin-bottom: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;`;
  40.  
  41. const getTextButton = document.createElement('button');
  42. getTextButton.textContent = 'Lấy Text';
  43. getTextButton.style.cssText = `flex: 1; padding: 8px; margin-right: 5px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;`;
  44.  
  45. const nextChapterButton = document.createElement('button');
  46. nextChapterButton.textContent = 'Chương Tiếp';
  47. nextChapterButton.style.cssText = `flex: 1; padding: 8px; margin-left: 5px; background-color: #ff9800; color: white; border: none; border-radius: 4px; cursor: pointer;`;
  48.  
  49. const clearButton = document.createElement('button');
  50. clearButton.textContent = 'Xoá';
  51. clearButton.style.cssText = `flex: 1; padding: 8px; margin-right: 5px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;`;
  52.  
  53. const copyButton = document.createElement('button');
  54. copyButton.textContent = 'Sao Chép';
  55. copyButton.style.cssText = `flex: 1; padding: 8px; margin-left: 5px; background-color: #9c27b0; color: white; border: none; border-radius: 4px; cursor: pointer;`;
  56.  
  57. const buttonRow1 = document.createElement('div');
  58. buttonRow1.style.cssText = `display: flex; justify-content: space-between; margin-bottom: 10px;`;
  59. buttonRow1.appendChild(getTextButton);
  60. buttonRow1.appendChild(nextChapterButton);
  61.  
  62. const buttonRow2 = document.createElement('div');
  63. buttonRow2.style.cssText = `display: flex; justify-content: space-between;`;
  64. buttonRow2.appendChild(clearButton);
  65. buttonRow2.appendChild(copyButton);
  66.  
  67. panel.appendChild(textarea);
  68. panel.appendChild(startButton);
  69. panel.appendChild(buttonRow1);
  70. panel.appendChild(buttonRow2);
  71. document.body.appendChild(panel);
  72.  
  73. const savedContent = localStorage.getItem('chapterDownloaderContent');
  74. if (savedContent) textarea.value = savedContent;
  75.  
  76. function saveContent() {
  77. localStorage.setItem('chapterDownloaderContent', textarea.value);
  78. }
  79. textarea.addEventListener('input', saveContent);
  80.  
  81. function extractChapterContent() {
  82. const titleElement = document.querySelector('h2');
  83. const contentElement = document.querySelector('.box-chap');
  84. if (titleElement && contentElement) {
  85. const title = titleElement.innerText.trim();
  86. const content = contentElement.innerText.trim();
  87. textarea.value += `\n\n------------\n\n${title}\n\n${content}`;
  88. saveContent();
  89. return true;
  90. }
  91. return false;
  92. }
  93.  
  94. function goToNextChapter() {
  95. const next = document.querySelector('.bot-next_chap.bot-control') ||
  96. [...document.querySelectorAll('a')].find(a => /tiếp|sau|next/i.test(a.textContent));
  97. if (next) {
  98. next.click();
  99. return true;
  100. }
  101. return false;
  102. }
  103.  
  104. function waitForNextChapterLoaded(oldTitle) {
  105. return new Promise((resolve, reject) => {
  106. const interval = setInterval(() => {
  107. const newTitle = document.querySelector('h2')?.innerText.trim();
  108. if (newTitle && newTitle !== oldTitle) {
  109. clearInterval(interval);
  110. resolve();
  111. }
  112. }, 500);
  113. setTimeout(() => {
  114. clearInterval(interval);
  115. reject('Timeout');
  116. }, 10000);
  117. });
  118. }
  119.  
  120. let isAutoDownloading = false;
  121. async function startAutoDownloading() {
  122. if (!isAutoDownloading) return;
  123. const titleEl = document.querySelector('h2');
  124. const oldTitle = titleEl?.innerText.trim();
  125. if (downloadedCount >= MAX_CHAPTERS) {
  126. isAutoDownloading = false;
  127. startButton.textContent = 'Bắt đầu';
  128. startButton.style.backgroundColor = '#4CAF50';
  129. return;
  130. }
  131. if (extractChapterContent()) {
  132. downloadedCount++;
  133. if (goToNextChapter()) {
  134. try {
  135. await waitForNextChapterLoaded(oldTitle);
  136. setTimeout(startAutoDownloading, 1000);
  137. } catch (err) {
  138. isAutoDownloading = false;
  139. startButton.textContent = 'Bắt đầu';
  140. startButton.style.backgroundColor = '#4CAF50';
  141. }
  142. } else {
  143. isAutoDownloading = false;
  144. startButton.textContent = 'Bắt đầu';
  145. startButton.style.backgroundColor = '#4CAF50';
  146. }
  147. }
  148. }
  149.  
  150. startButton.addEventListener('click', () => {
  151. isAutoDownloading = !isAutoDownloading;
  152. startButton.textContent = isAutoDownloading ? 'Dừng' : 'Bắt đầu';
  153. startButton.style.backgroundColor = isAutoDownloading ? '#f44336' : '#4CAF50';
  154. if (isAutoDownloading) startAutoDownloading();
  155. });
  156.  
  157. getTextButton.addEventListener('click', extractChapterContent);
  158. nextChapterButton.addEventListener('click', goToNextChapter);
  159. clearButton.addEventListener('click', () => { textarea.value = ''; saveContent(); });
  160. copyButton.addEventListener('click', () => {
  161. textarea.select();
  162. document.execCommand('copy');
  163. const notification = document.createElement('div');
  164. notification.textContent = 'Đã sao chép!';
  165. notification.style.cssText = `position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #000a; color: white; padding: 10px 20px; border-radius: 4px; z-index: 10000;`;
  166. document.body.appendChild(notification);
  167. setTimeout(() => document.body.removeChild(notification), 2000);
  168. });
  169. })();