Chapter Downloader

Add a control panel for novel reading on truyen.tangthuvien.net

当前为 2025-03-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Chapter Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.9
  5. // @description Add a control panel for novel reading on truyen.tangthuvien.net
  6. // @author You
  7. // @match https://truyen.tangthuvien.net/doc-truyen/*
  8. // @match https://truyen.tangthuvien.net/doc-truyen/*/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Create panel container
  16. const panel = document.createElement('div');
  17. panel.id = 'reader-panel';
  18. panel.style.cssText = `
  19. position: fixed;
  20. top: 10%;
  21. right: 10px;
  22. width: 300px;
  23. background-color: #f8f8f8;
  24. border: 1px solid #ccc;
  25. border-radius: 5px;
  26. padding: 10px;
  27. z-index: 9999;
  28. box-shadow: 0 0 10px rgba(0,0,0,0.2);
  29. font-family: Arial, sans-serif;
  30. `;
  31.  
  32. // Create textarea
  33. const textarea = document.createElement('textarea');
  34. textarea.id = 'content-textarea';
  35. textarea.style.cssText = `
  36. width: 100%;
  37. height: 300px;
  38. margin-bottom: 10px;
  39. padding: 8px;
  40. border: 1px solid #ddd;
  41. border-radius: 4px;
  42. resize: vertical;
  43. `;
  44.  
  45. // Create start button
  46. const startButton = document.createElement('button');
  47. startButton.textContent = 'Bắt đầu';
  48. startButton.style.cssText = `
  49. width: 100%;
  50. padding: 8px;
  51. margin-bottom: 10px;
  52. background-color: #4CAF50;
  53. color: white;
  54. border: none;
  55. border-radius: 4px;
  56. cursor: pointer;
  57. `;
  58.  
  59. // Create container for getTextButton and nextChapterButton (same row)
  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. // Create get text button
  68. const getTextButton = document.createElement('button');
  69. getTextButton.textContent = 'Lấy Text';
  70. getTextButton.style.cssText = `
  71. flex: 1;
  72. padding: 8px;
  73. margin-right: 5px;
  74. background-color: #2196F3;
  75. color: white;
  76. border: none;
  77. border-radius: 4px;
  78. cursor: pointer;
  79. `;
  80.  
  81. // Create next chapter button
  82. const nextChapterButton = document.createElement('button');
  83. nextChapterButton.textContent = 'Chương Tiếp';
  84. nextChapterButton.style.cssText = `
  85. flex: 1;
  86. padding: 8px;
  87. margin-left: 5px;
  88. background-color: #ff9800;
  89. color: white;
  90. border: none;
  91. border-radius: 4px;
  92. cursor: pointer;
  93. `;
  94.  
  95. // Create container for clearButton and copyButton (same row)
  96. const buttonRow2 = document.createElement('div');
  97. buttonRow2.style.cssText = `
  98. display: flex;
  99. justify-content: space-between;
  100. `;
  101.  
  102. // Create clear button
  103. const clearButton = document.createElement('button');
  104. clearButton.textContent = 'Xoá';
  105. clearButton.style.cssText = `
  106. flex: 1;
  107. padding: 8px;
  108. margin-right: 5px;
  109. background-color: #f44336;
  110. color: white;
  111. border: none;
  112. border-radius: 4px;
  113. cursor: pointer;
  114. `;
  115.  
  116. // Create copy button
  117. const copyButton = document.createElement('button');
  118. copyButton.textContent = 'Sao Chép';
  119. copyButton.style.cssText = `
  120. flex: 1;
  121. padding: 8px;
  122. margin-left: 5px;
  123. background-color: #9c27b0;
  124. color: white;
  125. border: none;
  126. border-radius: 4px;
  127. cursor: pointer;
  128. `;
  129.  
  130. // Add buttons to their respective row containers
  131. buttonRow1.appendChild(getTextButton);
  132. buttonRow1.appendChild(nextChapterButton);
  133. buttonRow2.appendChild(clearButton);
  134. buttonRow2.appendChild(copyButton);
  135.  
  136. // Add all elements to panel
  137. panel.appendChild(textarea);
  138. panel.appendChild(startButton);
  139. panel.appendChild(buttonRow1);
  140. panel.appendChild(buttonRow2);
  141.  
  142. // Add panel to body
  143. document.body.appendChild(panel);
  144.  
  145. // Load saved content from localStorage if exists
  146. const savedContent = localStorage.getItem('chapterDownloaderContent');
  147. if (savedContent) {
  148. textarea.value = savedContent;
  149. }
  150.  
  151. // Function to save content to localStorage
  152. function saveContent() {
  153. localStorage.setItem('chapterDownloaderContent', textarea.value);
  154. console.log('Content saved to localStorage');
  155. }
  156.  
  157. // Auto save when textarea value changes
  158. textarea.addEventListener('input', saveContent);
  159.  
  160. // Function to extract and add chapter content
  161. function extractChapterContent() {
  162. // Get chapter title
  163. const titleElement = document.querySelector('h2');
  164. // Get chapter content
  165. const contentElement = document.querySelector('.box-chap');
  166. if (titleElement && contentElement) {
  167. // Append to existing content rather than replacing
  168. const title = titleElement.innerText.trim();
  169. const content = contentElement.innerText.trim();
  170. // Append with proper formatting
  171. if (textarea.value) {
  172. textarea.value += '\n\n------------\n\n' + title + '\n\n' + content;
  173. } else {
  174. textarea.value = title + '\n\n' + content;
  175. }
  176. // Save content after adding new chapter
  177. saveContent();
  178. return true;
  179. } else {
  180. console.log('Could not find title or content elements');
  181. return false;
  182. }
  183. }
  184.  
  185. // Function to navigate to next chapter
  186. function goToNextChapter() {
  187. // Find the next chapter button with the correct selector
  188. const nextChapterLink = document.querySelector('.bot-next_chap.bot-control');
  189. if (nextChapterLink) {
  190. console.log('Found next chapter link, navigating...');
  191. nextChapterLink.click();
  192. return true;
  193. } else {
  194. console.log('Could not find next chapter link with class ".bot-next_chap.bot-control"');
  195. // Try alternative selectors if the main one doesn't work
  196. const alternativeNextLinks = document.querySelectorAll('a[href*="chuong"]');
  197. for (const link of alternativeNextLinks) {
  198. if (link.textContent.includes('tiếp') || link.textContent.includes('sau') || link.textContent.includes('next')) {
  199. console.log('Found alternative next chapter link, navigating...');
  200. link.click();
  201. return true;
  202. }
  203. }
  204. return false;
  205. }
  206. }
  207.  
  208. // Variable to track auto-downloading state
  209. let isAutoDownloading = false;
  210. // Function to handle automatic downloading
  211. function startAutoDownloading() {
  212. if (!isAutoDownloading) return;
  213. console.log('Auto-downloading: Extracting chapter content...');
  214. if (extractChapterContent()) {
  215. // After extracting, wait 1 second then navigate to next chapter
  216. setTimeout(() => {
  217. if (!isAutoDownloading) return;
  218. console.log('Auto-downloading: Navigating to next chapter...');
  219. if (goToNextChapter()) {
  220. // After navigation, wait 2 seconds for page to load then extract again
  221. setTimeout(() => {
  222. if (isAutoDownloading) {
  223. startAutoDownloading();
  224. }
  225. }, 2000); // Wait 2 seconds for page to load before extracting next chapter
  226. } else {
  227. console.log('Auto-downloading: Could not find next chapter, stopping.');
  228. isAutoDownloading = false;
  229. startButton.textContent = 'Bắt đầu';
  230. startButton.style.backgroundColor = '#4CAF50';
  231. }
  232. }, 1000); // Wait 1 second before navigating
  233. } else {
  234. console.log('Auto-downloading: Could not extract chapter content, stopping.');
  235. isAutoDownloading = false;
  236. startButton.textContent = 'Bắt đầu';
  237. startButton.style.backgroundColor = '#4CAF50';
  238. }
  239. }
  240. // Add event listeners
  241. startButton.addEventListener('click', () => {
  242. // Toggle auto-downloading state
  243. isAutoDownloading = !isAutoDownloading;
  244. if (isAutoDownloading) {
  245. console.log('Start button clicked: Beginning auto-download sequence');
  246. startButton.textContent = 'Dừng';
  247. startButton.style.backgroundColor = '#f44336'; // Red color to indicate active
  248. startAutoDownloading(); // Start the sequence
  249. } else {
  250. console.log('Start button clicked: Stopping auto-download sequence');
  251. startButton.textContent = 'Bắt đầu';
  252. startButton.style.backgroundColor = '#4CAF50'; // Green color when inactive
  253. }
  254. });
  255.  
  256. getTextButton.addEventListener('click', () => {
  257. console.log('Get text button clicked');
  258. extractChapterContent();
  259. });
  260.  
  261. nextChapterButton.addEventListener('click', () => {
  262. console.log('Next chapter button clicked');
  263. goToNextChapter();
  264. });
  265.  
  266. clearButton.addEventListener('click', () => {
  267. console.log('Clear button clicked');
  268. textarea.value = '';
  269. saveContent(); // Save empty content to localStorage
  270. });
  271.  
  272. copyButton.addEventListener('click', () => {
  273. console.log('Copy button clicked');
  274. textarea.select();
  275. document.execCommand('copy');
  276. // Show a temporary copy notification
  277. const notification = document.createElement('div');
  278. notification.textContent = 'Đã sao chép!';
  279. notification.style.cssText = `
  280. position: fixed;
  281. bottom: 20px;
  282. left: 50%;
  283. transform: translateX(-50%);
  284. background-color: rgba(0,0,0,0.8);
  285. color: white;
  286. padding: 10px 20px;
  287. border-radius: 4px;
  288. z-index: 10000;
  289. `;
  290. document.body.appendChild(notification);
  291. // Remove notification after 2 seconds
  292. setTimeout(() => {
  293. document.body.removeChild(notification);
  294. }, 2000);
  295. });
  296.  
  297. // Make panel draggable
  298. let isDragging = false;
  299. let offsetX, offsetY;
  300.  
  301. panel.addEventListener('mousedown', (e) => {
  302. if (e.target === panel) {
  303. isDragging = true;
  304. offsetX = e.clientX - panel.getBoundingClientRect().left;
  305. offsetY = e.clientY - panel.getBoundingClientRect().top;
  306. }
  307. });
  308.  
  309. document.addEventListener('mousemove', (e) => {
  310. if (isDragging) {
  311. panel.style.left = (e.clientX - offsetX) + 'px';
  312. panel.style.top = (e.clientY - offsetY) + 'px';
  313. panel.style.right = 'auto';
  314. }
  315. });
  316.  
  317. document.addEventListener('mouseup', () => {
  318. isDragging = false;
  319. });
  320.  
  321. // Add keyboard shortcuts
  322. document.addEventListener('keydown', (e) => {
  323. // Only handle if not typing in a text field
  324. if (e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'INPUT') {
  325. // Alt+G: Get text
  326. if (e.altKey && e.key === 'g') {
  327. extractChapterContent();
  328. e.preventDefault();
  329. }
  330. // Alt+N: Next chapter
  331. else if (e.altKey && e.key === 'n') {
  332. goToNextChapter();
  333. e.preventDefault();
  334. }
  335. // Alt+S: Toggle auto-downloading (same as clicking Start/Stop button)
  336. else if (e.altKey && e.key === 's') {
  337. // Simulate clicking the start button
  338. startButton.click();
  339. e.preventDefault();
  340. }
  341. // Alt+C: Copy text
  342. else if (e.altKey && e.key === 'c') {
  343. textarea.select();
  344. document.execCommand('copy');
  345. e.preventDefault();
  346. }
  347. }
  348. });
  349.  
  350. // Add information about keyboard shortcuts
  351. const shortcutsInfo = document.createElement('div');
  352. shortcutsInfo.style.cssText = `
  353. font-size: 11px;
  354. color: #666;
  355. margin-top: 10px;
  356. padding-top: 5px;
  357. border-top: 1px solid #ddd;
  358. `;
  359. shortcutsInfo.innerHTML = `
  360. <b>Phím tt:</b> Alt+G (Lấy text), Alt+N (Chương tiếp), Alt+S (Bắt đầu/Dng t động), Alt+C (Sao chép)
  361. `;
  362. panel.appendChild(shortcutsInfo);
  363.  
  364. console.log('Chapter Downloader script initialized successfully');
  365. })();