Chapter Downloader

Tải nội dung chương truyện từ TangThuVien

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

  1. // ==UserScript==
  2. // @name Chapter Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Tải nội dung chương truyện từ TangThuVien
  6. // @author Your name
  7. // @match https://truyen.tangthuvien.net/doc-truyen/*
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Thêm CSS cho khung soạn thảo
  15. GM_addStyle(`
  16. .story-editor-container {
  17. position: fixed;
  18. top: 20px;
  19. right: 20px;
  20. width: 400px;
  21. height: 80vh;
  22. background: white;
  23. border: 1px solid #ccc;
  24. padding: 10px;
  25. z-index: 9999;
  26. display: flex;
  27. flex-direction: column;
  28. gap: 10px;
  29. }
  30. .story-editor {
  31. flex: 1;
  32. width: 100%;
  33. resize: none;
  34. font-family: Arial, sans-serif;
  35. font-size: 14px;
  36. line-height: 1.6;
  37. padding: 10px;
  38. }
  39. .button-container {
  40. display: flex;
  41. gap: 10px;
  42. }
  43. button {
  44. padding: 8px 16px;
  45. cursor: pointer;
  46. background: #4CAF50;
  47. color: white;
  48. border: none;
  49. border-radius: 4px;
  50. }
  51. button:hover {
  52. background: #45a049;
  53. }
  54. .status-text {
  55. font-size: 12px;
  56. color: #666;
  57. }
  58. `);
  59.  
  60. // Lấy nội dung chương hiện tại
  61. function getCurrentChapter() {
  62. try {
  63. console.log('Bắt đầu lấy nội dung chương...');
  64.  
  65. // Tìm container chứa nội dung
  66. const container = document.querySelector('#article') || document.querySelector('.content-chapter');
  67. if (!container) {
  68. throw new Error('Không tìm thấy container chương');
  69. }
  70.  
  71. // Tìm tiêu đề với nhiều selector
  72. const titleSelectors = [
  73. '.chapter-title',
  74. 'h2.chapter-title',
  75. '.chapter-c h2',
  76. 'h2',
  77. '.content-chapter h2'
  78. ];
  79.  
  80. let titleElement = null;
  81. for (const selector of titleSelectors) {
  82. const element = container.querySelector(selector);
  83. if (element && element.textContent.trim()) {
  84. titleElement = element;
  85. console.log('Tìm thấy tiêu đề với selector:', selector);
  86. break;
  87. }
  88. }
  89.  
  90. // Tìm nội dung với nhiều selector
  91. const contentSelectors = [
  92. '.chapter-c',
  93. '.chapter-content',
  94. '.content-chapter'
  95. ];
  96.  
  97. let contentElement = null;
  98. for (const selector of contentSelectors) {
  99. const element = container.querySelector(selector);
  100. if (element && element.textContent.trim()) {
  101. contentElement = element;
  102. console.log('Tìm thấy nội dung với selector:', selector);
  103. break;
  104. }
  105. }
  106.  
  107. console.log('Elements tìm thấy:', {
  108. hasTitle: !!titleElement,
  109. hasContent: !!contentElement,
  110. titleHTML: titleElement?.outerHTML,
  111. contentPreview: contentElement?.textContent?.trim().substring(0, 100)
  112. });
  113.  
  114. if (!titleElement || !contentElement) {
  115. throw new Error('Không tìm thấy nội dung chương');
  116. }
  117.  
  118. // Lấy và làm sạch nội dung
  119. let title = titleElement.textContent.trim();
  120. let content = contentElement.textContent.trim();
  121.  
  122. // Loại bỏ các phần thừa
  123. content = content.replace(title, ''); // Xóa tiêu đề nếu trùng
  124. content = content.replace(/\[\s*\w+\s*\]/g, ''); // Xóa [xxx]
  125. content = content.replace(/[""]/g, '"'); // Chuẩn hóa dấu ngoặc kép
  126. content = content.replace(/\s+/g, ' '); // Chuẩn hóa khoảng trắng
  127. content = content.replace(/^\s+|\s+$/g, ''); // Trim
  128.  
  129. if (!title || !content) {
  130. throw new Error('Nội dung chương không hợp lệ');
  131. }
  132.  
  133. console.log('Đã lấy nội dung:', {
  134. title,
  135. contentLength: content.length,
  136. contentPreview: content.substring(0, 100) + '...'
  137. });
  138.  
  139. return { title, content };
  140. } catch (error) {
  141. console.error('Lỗi khi lấy nội dung chương:', error);
  142. updateStatus('Lỗi khi lấy nội dung chương: ' + error.message);
  143. return null;
  144. }
  145. }
  146.  
  147. // Tạo UI elements
  148. function createUI() {
  149. console.log('Tạo giao diện...');
  150.  
  151. const container = document.createElement('div');
  152. container.className = 'story-editor-container';
  153.  
  154. const editor = document.createElement('textarea');
  155. editor.className = 'story-editor';
  156. editor.placeholder = 'Nội dung truyện sẽ hiển thị ở đây...';
  157. editor.readOnly = true;
  158.  
  159. const buttonContainer = document.createElement('div');
  160. buttonContainer.className = 'button-container';
  161.  
  162. const downloadBtn = document.createElement('button');
  163. downloadBtn.textContent = 'Tải chương tiếp';
  164. downloadBtn.onclick = loadNextChapter;
  165.  
  166. const exportBtn = document.createElement('button');
  167. exportBtn.textContent = 'Xuất file TXT';
  168. exportBtn.onclick = exportToTxt;
  169.  
  170. buttonContainer.appendChild(downloadBtn);
  171. buttonContainer.appendChild(exportBtn);
  172.  
  173. const status = document.createElement('div');
  174. status.className = 'status-text';
  175.  
  176. container.appendChild(editor);
  177. container.appendChild(buttonContainer);
  178. container.appendChild(status);
  179. document.body.appendChild(container);
  180.  
  181. console.log('Đã tạo giao diện thành công');
  182. return { editor, downloadBtn, exportBtn, status };
  183. }
  184.  
  185. // Cập nhật trạng thái
  186. function updateStatus(message) {
  187. const status = document.querySelector('.status-text');
  188. if (status) {
  189. status.textContent = message;
  190. console.log('Trạng thái:', message);
  191. }
  192. }
  193.  
  194. // Thêm nội dung chương mới vào editor
  195. function appendChapterContent(editor, title, content) {
  196. try {
  197. console.log('Bắt đầu thêm nội dung chương mới');
  198. console.log('Nội dung hiện tại:', editor.value.length, 'ký tự');
  199.  
  200. // Thêm dấu phân cách nếu đã có nội dung
  201. const separator = editor.value ? '\n\n' + '-'.repeat(50) + '\n\n' : '';
  202. const newContent = title + '\n\n' + content;
  203.  
  204. // Thêm nội dung mới và giữ lại nội dung cũ
  205. editor.value = editor.value + separator + newContent;
  206.  
  207. // Cuộn xuống dưới để hiển thị nội dung mới
  208. editor.scrollTop = editor.scrollHeight;
  209.  
  210. console.log('Đã thêm thành công:', {
  211. addedLength: newContent.length,
  212. currentLength: editor.value.length
  213. });
  214. return true;
  215. } catch (error) {
  216. console.error('Lỗi khi thêm nội dung:', error);
  217. return false;
  218. }
  219. }
  220.  
  221. // Xuất nội dung ra file txt
  222. function exportToTxt() {
  223. try {
  224. console.log('Bắt đầu xuất file...');
  225. const editor = document.querySelector('.story-editor');
  226. if (!editor || !editor.value) {
  227. throw new Error('Không có nội dung để xuất');
  228. }
  229.  
  230. const blob = new Blob([editor.value], { type: 'text/plain;charset=utf-8' });
  231. const url = window.URL.createObjectURL(blob);
  232. const link = document.createElement('a');
  233.  
  234. // Lấy tên truyện từ URL
  235. const storyName = window.location.pathname.split('/')[2] || 'truyen';
  236.  
  237. link.href = url;
  238. link.download = `${storyName}.txt`;
  239. document.body.appendChild(link);
  240. link.click();
  241. document.body.removeChild(link);
  242. window.URL.revokeObjectURL(url);
  243.  
  244. updateStatus('Đã xuất file thành công!');
  245. console.log('Xuất file hoàn tất');
  246. } catch (error) {
  247. console.error('Lỗi khi xuất file:', error);
  248. updateStatus('Lỗi khi xuất file: ' + error.message);
  249. }
  250. }
  251.  
  252. // Chuyển đến chương tiếp theo
  253. function goToNextChapter() {
  254. const nextButton = document.querySelector('a.next-chap');
  255. if (nextButton && !nextButton.classList.contains('disabled') && nextButton.style.display !== 'none') {
  256. console.log('Nhấn nút chuyển chương tiếp theo');
  257. nextButton.click();
  258. return true;
  259. }
  260. console.log('Không tìm thấy nút chuyển chương hợp lệ');
  261. return false;
  262. }
  263.  
  264. // Kiểm tra xem có phải chương cuối không
  265. function isLastChapter() {
  266. const nextButton = document.querySelector('a.next-chap');
  267. console.log('Kiểm tra chương cuối:', {
  268. buttonExists: !!nextButton,
  269. isDisabled: nextButton?.classList.contains('disabled'),
  270. isHidden: nextButton?.style.display === 'none'
  271. });
  272. return !nextButton || nextButton.classList.contains('disabled') || nextButton.style.display === 'none';
  273. }
  274.  
  275. // Tải chương tiếp theo
  276. async function loadNextChapter() {
  277. try {
  278. console.log('Bắt đầu tải chương tiếp');
  279. updateStatus('Đang tải chương tiếp theo...');
  280.  
  281. const editor = document.querySelector('.story-editor');
  282. if (!editor) {
  283. throw new Error('Không tìm thấy khung soạn thảo');
  284. }
  285.  
  286. // Đợi 5 giây để nội dung được tải
  287. await new Promise(resolve => setTimeout(resolve, 5000));
  288.  
  289. // Lấy nội dung chương hiện tại
  290. const currentChapter = getCurrentChapter();
  291. if (!currentChapter) {
  292. throw new Error('Không thể lấy nội dung chương hiện tại');
  293. }
  294.  
  295. const { title, content } = currentChapter;
  296.  
  297. // Thêm nội dung mới vào cuối
  298. appendChapterContent(editor, title, content);
  299. updateStatus('Đã tải xong chương hiện tại');
  300.  
  301. // Kiểm tra nếu là chương cuối
  302. if (isLastChapter()) {
  303. console.log('Đã phát hiện chương cuối');
  304. updateStatus('Đã tải xong tất cả các chương! Đang xuất file...');
  305. setTimeout(exportToTxt, 1000);
  306. return;
  307. }
  308.  
  309. // Chuyển sang chương tiếp theo sau khi lấy xong chương hiện tại
  310. setTimeout(() => {
  311. if (goToNextChapter()) {
  312. setTimeout(loadNextChapter, 3000);
  313. }
  314. }, 1000);
  315. } catch (error) {
  316. console.error('Lỗi khi tải chương:', error);
  317. updateStatus('Lỗi khi tải chương: ' + error.message);
  318. }
  319. }
  320.  
  321. // Khởi tạo khi DOM đã sẵn sàng
  322. if (document.readyState === 'loading') {
  323. document.addEventListener('DOMContentLoaded', createUI);
  324. } else {
  325. createUI();
  326. }
  327.  
  328. console.log('TangThuVien Chapter Downloader đã khởi chạy');
  329. })();