Chapter Downloader

Add a control panel for novel reading on truyen.tangthuvien.net, auto download max 200 chapters

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

  1. // ==UserScript==
  2. // @name Chapter Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2
  5. // @description Add a control panel for novel reading on truyen.tangthuvien.net, auto download max 200 chapters
  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 isAutoDownloading = false;
  17. let downloadedCount = 0;
  18.  
  19. const panel = document.createElement('div');
  20. panel.id = 'reader-panel';
  21. panel.style.cssText = `
  22. position: fixed;
  23. top: 10%;
  24. right: 10px;
  25. width: 300px;
  26. background-color: #f8f8f8;
  27. border: 1px solid #ccc;
  28. border-radius: 5px;
  29. padding: 10px;
  30. z-index: 9999;
  31. box-shadow: 0 0 10px rgba(0,0,0,0.2);
  32. font-family: Arial, sans-serif;
  33. `;
  34.  
  35. const textarea = document.createElement('textarea');
  36. textarea.id = 'content-textarea';
  37. textarea.style.cssText = `
  38. width: 100%;
  39. height: 300px;
  40. margin-bottom: 10px;
  41. padding: 8px;
  42. border: 1px solid #ddd;
  43. border-radius: 4px;
  44. resize: vertical;
  45. `;
  46.  
  47. const startButton = document.createElement('button');
  48. startButton.textContent = 'Bắt đầu';
  49. startButton.style.cssText = `
  50. width: 100%;
  51. padding: 8px;
  52. margin-bottom: 10px;
  53. background-color: #4CAF50;
  54. color: white;
  55. border: none;
  56. border-radius: 4px;
  57. cursor: pointer;
  58. `;
  59.  
  60. const buttonRow1 = document.createElement('div');
  61. buttonRow1.style.cssText = `
  62. display: flex;
  63. justify-content: space-between;
  64. margin-bottom: 10px;
  65. `;
  66.  
  67. const getTextButton = document.createElement('button');
  68. getTextButton.textContent = 'Lấy Text';
  69. getTextButton.style.cssText = `
  70. flex: 1;
  71. padding: 8px;
  72. margin-right: 5px;
  73. background-color: #2196F3;
  74. color: white;
  75. border: none;
  76. border-radius: 4px;
  77. cursor: pointer;
  78. `;
  79.  
  80. const nextChapterButton = document.createElement('button');
  81. nextChapterButton.textContent = 'Chương Tiếp';
  82. nextChapterButton.style.cssText = `
  83. flex: 1;
  84. padding: 8px;
  85. margin-left: 5px;
  86. background-color: #ff9800;
  87. color: white;
  88. border: none;
  89. border-radius: 4px;
  90. cursor: pointer;
  91. `;
  92.  
  93. const buttonRow2 = document.createElement('div');
  94. buttonRow2.style.cssText = `
  95. display: flex;
  96. justify-content: space-between;
  97. `;
  98.  
  99. const clearButton = document.createElement('button');
  100. clearButton.textContent = 'Xoá';
  101. clearButton.style.cssText = `
  102. flex: 1;
  103. padding: 8px;
  104. margin-right: 5px;
  105. background-color: #f44336;
  106. color: white;
  107. border: none;
  108. border-radius: 4px;
  109. cursor: pointer;
  110. `;
  111.  
  112. const copyButton = document.createElement('button');
  113. copyButton.textContent = 'Sao Chép';
  114. copyButton.style.cssText = `
  115. flex: 1;
  116. padding: 8px;
  117. margin-left: 5px;
  118. background-color: #9c27b0;
  119. color: white;
  120. border: none;
  121. border-radius: 4px;
  122. cursor: pointer;
  123. `;
  124.  
  125. buttonRow1.appendChild(getTextButton);
  126. buttonRow1.appendChild(nextChapterButton);
  127. buttonRow2.appendChild(clearButton);
  128. buttonRow2.appendChild(copyButton);
  129.  
  130. panel.appendChild(textarea);
  131. panel.appendChild(startButton);
  132. panel.appendChild(buttonRow1);
  133. panel.appendChild(buttonRow2);
  134.  
  135. const savedContent = localStorage.getItem('chapterDownloaderContent');
  136. if (savedContent) {
  137. textarea.value = savedContent;
  138. }
  139.  
  140. function saveContent() {
  141. localStorage.setItem('chapterDownloaderContent', textarea.value);
  142. }
  143.  
  144. textarea.addEventListener('input', saveContent);
  145.  
  146. function extractChapterContent() {
  147. const titleElement = document.querySelector('h2');
  148. const contentElement = document.querySelector('.box-chap');
  149.  
  150. if (titleElement && contentElement) {
  151. const title = titleElement.innerText.trim();
  152. const content = contentElement.innerText.trim();
  153. if (textarea.value) {
  154. textarea.value += '\n\n------------\n\n' + title + '\n\n' + content;
  155. } else {
  156. textarea.value = title + '\n\n' + content;
  157. }
  158. saveContent();
  159. return true;
  160. }
  161. return false;
  162. }
  163.  
  164. function goToNextChapter() {
  165. const nextChapterLink = document.querySelector('.bot-next_chap.bot-control');
  166. if (nextChapterLink) {
  167. nextChapterLink.click();
  168. return true;
  169. } else {
  170. const alternativeNextLinks = document.querySelectorAll('a[href*="chuong"]');
  171. for (const link of alternativeNextLinks) {
  172. if (link.textContent.includes('tiếp') || link.textContent.includes('sau') || link.textContent.toLowerCase().includes('next')) {
  173. link.click();
  174. return true;
  175. }
  176. }
  177. return false;
  178. }
  179. }
  180.  
  181. function startAutoDownloading() {
  182. if (!isAutoDownloading) return;
  183.  
  184. console.log(`Auto-downloading: Extracting chapter ${downloadedCount + 1}`);
  185.  
  186. if (downloadedCount >= MAX_CHAPTERS) {
  187. console.log(`Auto-downloading: Đã ti ${MAX_CHAPTERS} chương, dng.`);
  188. isAutoDownloading = false;
  189. startButton.textContent = 'Bắt đầu';
  190. startButton.style.backgroundColor = '#4CAF50';
  191. return;
  192. }
  193.  
  194. if (extractChapterContent()) {
  195. downloadedCount++;
  196.  
  197. setTimeout(() => {
  198. if (!isAutoDownloading) return;
  199. if (goToNextChapter()) {
  200. setTimeout(() => {
  201. if (isAutoDownloading) {
  202. startAutoDownloading();
  203. }
  204. }, 2000);
  205. } else {
  206. isAutoDownloading = false;
  207. startButton.textContent = 'Bắt đầu';
  208. startButton.style.backgroundColor = '#4CAF50';
  209. }
  210. }, 1000);
  211. } else {
  212. isAutoDownloading = false;
  213. startButton.textContent = 'Bắt đầu';
  214. startButton.style.backgroundColor = '#4CAF50';
  215. }
  216. }
  217.  
  218. startButton.addEventListener('click', () => {
  219. isAutoDownloading = !isAutoDownloading;
  220. if (isAutoDownloading) {
  221. downloadedCount = 0;
  222. startButton.textContent = 'Dừng';
  223. startButton.style.backgroundColor = '#f44336';
  224. startAutoDownloading();
  225. } else {
  226. startButton.textContent = 'Bắt đầu';
  227. startButton.style.backgroundColor = '#4CAF50';
  228. }
  229. });
  230.  
  231. getTextButton.addEventListener('click', () => {
  232. if (!isAutoDownloading) {
  233. downloadedCount = 0;
  234. isAutoDownloading = true;
  235. startButton.textContent = 'Dừng';
  236. startButton.style.backgroundColor = '#f44336';
  237. startAutoDownloading();
  238. }
  239. });
  240.  
  241. nextChapterButton.addEventListener('click', () => {
  242. goToNextChapter();
  243. });
  244.  
  245. clearButton.addEventListener('click', () => {
  246. textarea.value = '';
  247. saveContent();
  248. });
  249.  
  250. copyButton.addEventListener('click', () => {
  251. textarea.select();
  252. document.execCommand('copy');
  253. const notification = document.createElement('div');
  254. notification.textContent = 'Đã sao chép!';
  255. notification.style.cssText = `
  256. position: fixed;
  257. bottom: 20px;
  258. left: 50%;
  259. transform: translateX(-50%);
  260. background-color: rgba(0,0,0,0.8);
  261. color: white;
  262. padding: 10px 20px;
  263. border-radius: 4px;
  264. z-index: 10000;
  265. `;
  266. document.body.appendChild(notification);
  267. setTimeout(() => {
  268. document.body.removeChild(notification);
  269. }, 2000);
  270. });
  271.  
  272. let isDragging = false;
  273. let offsetX, offsetY;
  274.  
  275. panel.addEventListener('mousedown', (e) => {
  276. if (e.target === panel) {
  277. isDragging = true;
  278. offsetX = e.clientX - panel.getBoundingClientRect().left;
  279. offsetY = e.clientY - panel.getBoundingClientRect().top;
  280. }
  281. });
  282.  
  283. document.addEventListener('mousemove', (e) => {
  284. if (isDragging) {
  285. panel.style.left = (e.clientX - offsetX) + 'px';
  286. panel.style.top = (e.clientY - offsetY) + 'px';
  287. panel.style.right = 'auto';
  288. }
  289. });
  290.  
  291. document.addEventListener('mouseup', () => {
  292. isDragging = false;
  293. });
  294.  
  295. const shortcutsInfo = document.createElement('div');
  296. shortcutsInfo.style.cssText = `
  297. font-size: 11px;
  298. color: #666;
  299. margin-top: 10px;
  300. padding-top: 5px;
  301. border-top: 1px solid #ddd;
  302. `;
  303. shortcutsInfo.innerHTML = `
  304. <b>Phím tt:</b> Alt+G (Ly text), Alt+N (Chương tiếp), Alt+S (T động), Alt+C (Sao chép)
  305. `;
  306. panel.appendChild(shortcutsInfo);
  307.  
  308. document.body.appendChild(panel);
  309. })();