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.3
  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. // @match https://truyen.tangthuvien.vn/doc-truyen/*/*
  9. // @match https://tangthuvien.vn/doc-truyen/*/*
  10. // @match https://tangthuvien.com/doc-truyen/*/*
  11. // @match https://*.tangthuvien.*/doc-truyen/*/*
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Thêm CSS cho khung soạn thảo
  19. GM_addStyle(`
  20. .story-editor-container {
  21. position: fixed;
  22. top: 20px;
  23. right: 20px;
  24. width: 400px;
  25. height: 80vh;
  26. background: white;
  27. border: 1px solid #ccc;
  28. padding: 10px;
  29. z-index: 9999;
  30. display: flex;
  31. flex-direction: column;
  32. gap: 10px;
  33. }
  34. .story-editor {
  35. flex: 1;
  36. width: 100%;
  37. resize: none;
  38. font-family: Arial, sans-serif;
  39. font-size: 14px;
  40. line-height: 1.6;
  41. padding: 10px;
  42. }
  43. .button-container {
  44. display: flex;
  45. gap: 10px;
  46. }
  47. button {
  48. padding: 8px 16px;
  49. cursor: pointer;
  50. background: #4CAF50;
  51. color: white;
  52. border: none;
  53. border-radius: 4px;
  54. }
  55. button:hover {
  56. background: #45a049;
  57. }
  58. .status-text {
  59. font-size: 12px;
  60. color: #666;
  61. }
  62. `);
  63.  
  64. // Log tất cả các elements có thể chứa nội dung
  65. function logPossibleElements() {
  66. const elements = document.querySelectorAll('*');
  67. const contentElements = Array.from(elements).filter(el => {
  68. const text = el.textContent?.trim();
  69. return text && text.length > 100;
  70. });
  71.  
  72. console.log('Các elements có thể chứa nội dung:',
  73. contentElements.map(el => ({
  74. tagName: el.tagName,
  75. className: el.className,
  76. id: el.id,
  77. textLength: el.textContent?.trim().length,
  78. firstChars: el.textContent?.trim().substring(0, 50)
  79. }))
  80. );
  81. }
  82.  
  83. // Lấy nội dung chương hiện tại
  84. async function getCurrentChapter() {
  85. try {
  86. console.log('Bắt đầu lấy nội dung chương...');
  87. logPossibleElements();
  88.  
  89. // Đợi nội dung tải xong (tăng thời gian chờ lên 5 giây)
  90. console.log('Đợi 5 giây để nội dung tải hoàn tất...');
  91. await new Promise(resolve => setTimeout(resolve, 5000));
  92.  
  93. // Tìm container chính - mở rộng bộ chọn để phù hợp với TangThuVien
  94. const mainContainer = document.querySelector('#article') ||
  95. document.querySelector('.content-chapter') ||
  96. document.querySelector('.chapter-content') ||
  97. document.querySelector('.box-chap') ||
  98. document.querySelector('.content') ||
  99. document.querySelector('.chapter');
  100.  
  101. console.log('Container chính:', {
  102. found: !!mainContainer,
  103. id: mainContainer?.id,
  104. className: mainContainer?.className
  105. });
  106.  
  107. // Nếu không tìm thấy container chính, thử tìm trực tiếp trong document
  108. // TangThuVien có thể không tuân theo cấu trúc container>tiêu đề+nội dung
  109. if (!mainContainer) {
  110. console.log('Không tìm thấy container chính, thử tìm trực tiếp trong document');
  111. }
  112.  
  113. // Tìm tiêu đề
  114. const titleSelectors = [
  115. 'h2.chapter-title',
  116. '.chapter-title',
  117. '.truyen-title',
  118. 'h2.title',
  119. '.content-chapter h2',
  120. '.chapter h1',
  121. '.title-chapter',
  122. '.chapter-title h2',
  123. 'h1.title',
  124. 'header h1'
  125. ];
  126.  
  127. let titleElement;
  128. // Tìm trong container chính nếu có
  129. if (mainContainer) {
  130. for (const selector of titleSelectors) {
  131. titleElement = mainContainer.querySelector(selector);
  132. if (titleElement?.textContent?.trim()) {
  133. console.log('Tìm thấy tiêu đề với selector trong container:', selector);
  134. break;
  135. }
  136. }
  137. }
  138. // Nếu không tìm thấy trong container, tìm trong toàn bộ document
  139. if (!titleElement || !titleElement?.textContent?.trim()) {
  140. for (const selector of titleSelectors) {
  141. titleElement = document.querySelector(selector);
  142. if (titleElement?.textContent?.trim()) {
  143. console.log('Tìm thấy tiêu đề với selector trong document:', selector);
  144. break;
  145. }
  146. }
  147. }
  148.  
  149. // Tìm nội dung
  150. const contentSelectors = [
  151. '.chapter-c',
  152. '.chapter-content',
  153. '.content-chapter',
  154. '.box-chap',
  155. '.content',
  156. '#content',
  157. '.chapter-detail',
  158. '.chapter-text',
  159. '.chapter__content',
  160. '.ttv-chapter-content'
  161. ];
  162.  
  163. let contentElement;
  164. // Tìm trong container chính nếu có
  165. if (mainContainer) {
  166. for (const selector of contentSelectors) {
  167. contentElement = mainContainer.querySelector(selector);
  168. if (contentElement?.textContent?.trim()) {
  169. console.log('Tìm thấy nội dung với selector trong container:', selector);
  170. break;
  171. }
  172. }
  173. }
  174. // Nếu không tìm thấy trong container, tìm trong toàn bộ document
  175. if (!contentElement || !contentElement?.textContent?.trim()) {
  176. for (const selector of contentSelectors) {
  177. contentElement = document.querySelector(selector);
  178. if (contentElement?.textContent?.trim()) {
  179. console.log('Tìm thấy nội dung với selector trong document:', selector);
  180. break;
  181. }
  182. }
  183. }
  184.  
  185. // Xử lý trường hợp không tìm thấy tiêu đề hoặc nội dung
  186. if (!titleElement && !contentElement) {
  187. console.error('Không tìm thấy cả tiêu đề và nội dung');
  188. // Thử phương pháp cuối cùng - lấy toàn bộ văn bản của trang
  189. const bodyText = document.body.innerText;
  190. if (bodyText && bodyText.length > 500) { // Giả sử trang có ít nhất 500 ký tự
  191. console.log('Thử phương pháp cuối cùng - lấy toàn bộ văn bản');
  192. // Lấy tiêu đề từ thẻ title của trang
  193. const pageTitle = document.title.trim();
  194. return {
  195. title: pageTitle,
  196. content: bodyText
  197. };
  198. }
  199. throw new Error('Không tìm thấy nội dung hoặc tiêu đề');
  200. }
  201. if (!titleElement) {
  202. console.log('Không tìm thấy tiêu đề, sử dụng title của trang');
  203. const pageTitle = document.title.trim();
  204. titleElement = { textContent: pageTitle };
  205. }
  206. if (!contentElement) {
  207. console.error('Không tìm thấy nội dung:', {
  208. hasTitle: !!titleElement,
  209. titleHTML: titleElement?.outerHTML
  210. });
  211. throw new Error('Không tìm thấy nội dung');
  212. }
  213.  
  214. console.log('Tìm thấy cả tiêu đề và nội dung:', {
  215. hasTitle: !!titleElement,
  216. hasContent: !!contentElement,
  217. titlePreview: titleElement?.textContent?.trim().substring(0, 50),
  218. contentPreview: contentElement?.textContent?.trim().substring(0, 100)
  219. });
  220.  
  221. // Xử lý nội dung
  222. let title = titleElement.textContent.trim();
  223. let content = contentElement.textContent.trim();
  224.  
  225. // Làm sạch nội dung
  226. content = content.replace(title, ''); // Xóa tiêu đề khỏi nội dung
  227. content = content.replace(/\[\s*\w+\s*\]/g, ''); // Xóa [xxx]
  228. content = content.replace(/\[.*?\]/g, ''); // Xóa tất cả nội dung trong ngoặc vuông
  229. content = content.replace(/[""]/g, '"'); // Chuẩn hóa dấu ngoặc kép
  230. content = content.replace(/\s+/g, ' ').trim(); // Chuẩn hóa khoảng trắng
  231. // Loại bỏ các đoạn quảng cáo phổ biến
  232. content = content.replace(/Truyện\s+VIP\s+\S+/gi, '');
  233. content = content.replace(/Đọc\s+truyện\s+tại\s+\S+/gi, '');
  234. content = content.replace(/Tham\s+gia\s+group\s+\S+/gi, '');
  235. content = content.replace(/nguồn\s*:\s*\S+/gi, '');
  236.  
  237. console.log('Đã lấy nội dung:', {
  238. title,
  239. contentLength: content.length,
  240. contentPreview: content.substring(0, 100) + '...'
  241. });
  242.  
  243. return { title, content };
  244. } catch (error) {
  245. console.error('Lỗi khi lấy nội dung:', error);
  246. return null;
  247. }
  248. }
  249.  
  250. // Thêm nội dung chương mới vào editor
  251. function appendChapterContent(editor, title, content) {
  252. try {
  253. if (!editor || !title || !content) {
  254. console.error('Input không hợp lệ:', {
  255. hasEditor: !!editor,
  256. hasTitle: !!title,
  257. hasContent: !!content
  258. });
  259. return false;
  260. }
  261.  
  262. // Thêm dấu phân cách nếu đã có nội dung
  263. const separator = editor.value ? '\n\n' + '-'.repeat(50) + '\n\n' : '';
  264. const newContent = title + '\n\n' + content;
  265.  
  266. // Nối tiếp nội dung vào cuối
  267. editor.value += separator + newContent;
  268. editor.scrollTop = editor.scrollHeight;
  269.  
  270. console.log('Đã thêm nội dung:', {
  271. previousLength: editor.value.length - (separator + newContent).length,
  272. addedLength: newContent.length,
  273. newTotalLength: editor.value.length
  274. });
  275.  
  276. return true;
  277. } catch (error) {
  278. console.error('Lỗi khi thêm nội dung:', error);
  279. return false;
  280. }
  281. }
  282.  
  283. // Chuyển đến chương tiếp theo
  284. function goToNextChapter() {
  285. const nextButton = document.querySelector('a.next-chap');
  286. if (nextButton && !nextButton.classList.contains('disabled') && nextButton.style.display !== 'none') {
  287. console.log('Chuyển đến chương tiếp theo');
  288. nextButton.click();
  289. return true;
  290. }
  291. console.log('Không tìm thấy nút next hợp lệ');
  292. return false;
  293. }
  294.  
  295. // Tải chương tiếp theo
  296. async function loadNextChapter() {
  297. try {
  298. console.log('Bắt đầu tải chương tiếp...');
  299.  
  300. const editor = document.querySelector('.story-editor');
  301. if (!editor) {
  302. throw new Error('Không tìm thấy editor');
  303. }
  304.  
  305. // Đợi nội dung tải
  306. const chapterData = await getCurrentChapter();
  307. if (!chapterData) {
  308. throw new Error('Không lấy được nội dung chương');
  309. }
  310.  
  311. // Thêm nội dung vào editor
  312. if (!appendChapterContent(editor, chapterData.title, chapterData.content)) {
  313. throw new Error('Không thêm được nội dung vào editor');
  314. }
  315.  
  316. // Tự động tải chương tiếp
  317. setTimeout(() => {
  318. if (goToNextChapter()) {
  319. setTimeout(loadNextChapter, 3000);
  320. }
  321. }, 1000);
  322.  
  323. } catch (error) {
  324. console.error('Lỗi khi tải chương:', error);
  325. }
  326. }
  327.  
  328. // Xuất nội dung ra file txt
  329. function exportToTxt() {
  330. try {
  331. const editor = document.querySelector('.story-editor');
  332. if (!editor?.value) {
  333. throw new Error('Không có nội dung để xuất');
  334. }
  335.  
  336. const blob = new Blob([editor.value], { type: 'text/plain;charset=utf-8' });
  337. const url = window.URL.createObjectURL(blob);
  338. const link = document.createElement('a');
  339.  
  340. const storyName = window.location.pathname.split('/')[2] || 'truyen';
  341. link.href = url;
  342. link.download = `${storyName}.txt`;
  343.  
  344. document.body.appendChild(link);
  345. link.click();
  346. document.body.removeChild(link);
  347. window.URL.revokeObjectURL(url);
  348.  
  349. console.log('Đã xuất file thành công');
  350. } catch (error) {
  351. console.error('Lỗi khi xuất file:', error);
  352. }
  353. }
  354.  
  355. // Tạo giao diện
  356. function createUI() {
  357. console.log('Tạo giao diện...');
  358.  
  359. const container = document.createElement('div');
  360. container.className = 'story-editor-container';
  361.  
  362. const editor = document.createElement('textarea');
  363. editor.className = 'story-editor';
  364. editor.placeholder = 'Nội dung truyện sẽ hiển thị ở đây...';
  365. editor.readOnly = true;
  366.  
  367. const buttonContainer = document.createElement('div');
  368. buttonContainer.className = 'button-container';
  369.  
  370. const downloadBtn = document.createElement('button');
  371. downloadBtn.textContent = 'Tải chương tiếp';
  372. downloadBtn.onclick = loadNextChapter;
  373.  
  374. const exportBtn = document.createElement('button');
  375. exportBtn.textContent = 'Xuất file TXT';
  376. exportBtn.onclick = exportToTxt;
  377.  
  378. buttonContainer.appendChild(downloadBtn);
  379. buttonContainer.appendChild(exportBtn);
  380.  
  381. container.appendChild(editor);
  382. container.appendChild(buttonContainer);
  383. document.body.appendChild(container);
  384.  
  385. console.log('Đã tạo giao diện thành công');
  386. }
  387.  
  388. // Khởi tạo script
  389. if (document.readyState === 'loading') {
  390. document.addEventListener('DOMContentLoaded', createUI);
  391. } else {
  392. createUI();
  393. }
  394.  
  395. console.log('TangThuVien Chapter Downloader đã khởi chạy');
  396. })();