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.8
  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. // Add event listeners
  209. startButton.addEventListener('click', () => {
  210. console.log('Start button clicked');
  211. // Extract current chapter and then navigate to next
  212. if (extractChapterContent()) {
  213. // Add a small delay before navigating to next chapter
  214. setTimeout(() => {
  215. goToNextChapter();
  216. }, 1000);
  217. }
  218. });
  219.  
  220. getTextButton.addEventListener('click', () => {
  221. console.log('Get text button clicked');
  222. extractChapterContent();
  223. });
  224.  
  225. nextChapterButton.addEventListener('click', () => {
  226. console.log('Next chapter button clicked');
  227. goToNextChapter();
  228. });
  229.  
  230. clearButton.addEventListener('click', () => {
  231. console.log('Clear button clicked');
  232. textarea.value = '';
  233. saveContent(); // Save empty content to localStorage
  234. });
  235.  
  236. copyButton.addEventListener('click', () => {
  237. console.log('Copy button clicked');
  238. textarea.select();
  239. document.execCommand('copy');
  240. // Show a temporary copy notification
  241. const notification = document.createElement('div');
  242. notification.textContent = 'Đã sao chép!';
  243. notification.style.cssText = `
  244. position: fixed;
  245. bottom: 20px;
  246. left: 50%;
  247. transform: translateX(-50%);
  248. background-color: rgba(0,0,0,0.8);
  249. color: white;
  250. padding: 10px 20px;
  251. border-radius: 4px;
  252. z-index: 10000;
  253. `;
  254. document.body.appendChild(notification);
  255. // Remove notification after 2 seconds
  256. setTimeout(() => {
  257. document.body.removeChild(notification);
  258. }, 2000);
  259. });
  260.  
  261. // Make panel draggable
  262. let isDragging = false;
  263. let offsetX, offsetY;
  264.  
  265. panel.addEventListener('mousedown', (e) => {
  266. if (e.target === panel) {
  267. isDragging = true;
  268. offsetX = e.clientX - panel.getBoundingClientRect().left;
  269. offsetY = e.clientY - panel.getBoundingClientRect().top;
  270. }
  271. });
  272.  
  273. document.addEventListener('mousemove', (e) => {
  274. if (isDragging) {
  275. panel.style.left = (e.clientX - offsetX) + 'px';
  276. panel.style.top = (e.clientY - offsetY) + 'px';
  277. panel.style.right = 'auto';
  278. }
  279. });
  280.  
  281. document.addEventListener('mouseup', () => {
  282. isDragging = false;
  283. });
  284.  
  285. // Add keyboard shortcuts
  286. document.addEventListener('keydown', (e) => {
  287. // Only handle if not typing in a text field
  288. if (e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'INPUT') {
  289. // Alt+G: Get text
  290. if (e.altKey && e.key === 'g') {
  291. extractChapterContent();
  292. e.preventDefault();
  293. }
  294. // Alt+N: Next chapter
  295. else if (e.altKey && e.key === 'n') {
  296. goToNextChapter();
  297. e.preventDefault();
  298. }
  299. // Alt+S: Start (get text and go to next)
  300. else if (e.altKey && e.key === 's') {
  301. if (extractChapterContent()) {
  302. setTimeout(() => {
  303. goToNextChapter();
  304. }, 1000);
  305. }
  306. e.preventDefault();
  307. }
  308. // Alt+C: Copy text
  309. else if (e.altKey && e.key === 'c') {
  310. textarea.select();
  311. document.execCommand('copy');
  312. e.preventDefault();
  313. }
  314. }
  315. });
  316.  
  317. // Add information about keyboard shortcuts
  318. const shortcutsInfo = document.createElement('div');
  319. shortcutsInfo.style.cssText = `
  320. font-size: 11px;
  321. color: #666;
  322. margin-top: 10px;
  323. padding-top: 5px;
  324. border-top: 1px solid #ddd;
  325. `;
  326. shortcutsInfo.innerHTML = `
  327. <b>Phím tt:</b> Alt+G (Ly text), Alt+N (Chương tiếp), Alt+S (Bt đầu), Alt+C (Sao chép)
  328. `;
  329. panel.appendChild(shortcutsInfo);
  330.  
  331. console.log('Chapter Downloader script initialized successfully');
  332. })();