Tải toàn bộ chương từ danh sách (Tangthuvien)

Tự động tải toàn bộ chương từ phần "Danh sách chương" trên Tangthuvien

当前为 2025-04-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Tải toàn bộ chương từ danh sách (Tangthuvien)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Tự động tải toàn bộ chương từ phần "Danh sách chương" trên Tangthuvien
  6. // @match https://truyen.tangthuvien.vn/doc-truyen/*
  7. // @grant GM_xmlhttpRequest
  8. // @connect truyen.tangthuvien.vn
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. if (!location.href.includes('/doc-truyen/')) return;
  15.  
  16. // Giao diện
  17. const textarea = document.createElement('textarea');
  18. textarea.style.cssText = 'width:100%; height:300px; margin-bottom:10px; white-space: pre-wrap;';
  19.  
  20. const container = document.createElement('div');
  21. container.style.cssText = `
  22. position: fixed; top: 10%; right: 10px; z-index: 9999;
  23. width: 400px; background: white; border: 1px solid #ccc;
  24. padding: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.3);
  25. `;
  26.  
  27. const startBtn = document.createElement('button');
  28. startBtn.textContent = 'Bắt đầu tải';
  29. startBtn.style.cssText = 'width: 100%; padding: 8px; margin-bottom: 10px;';
  30.  
  31. const copyBtn = document.createElement('button');
  32. copyBtn.textContent = 'Sao chép';
  33. copyBtn.style.cssText = 'width: 100%; padding: 8px;';
  34.  
  35. container.appendChild(textarea);
  36. container.appendChild(startBtn);
  37. container.appendChild(copyBtn);
  38. document.body.appendChild(container);
  39.  
  40. function sleep(ms) {
  41. return new Promise(resolve => setTimeout(resolve, ms));
  42. }
  43.  
  44. function fetchChapter(url) {
  45. return new Promise((resolve) => {
  46. GM_xmlhttpRequest({
  47. method: "GET",
  48. url: url,
  49. onload: function (response) {
  50. const parser = new DOMParser();
  51. const doc = parser.parseFromString(response.responseText, 'text/html');
  52. const title = doc.querySelector('h2')?.innerText.trim() || 'Không tiêu đề';
  53. const content = doc.querySelector('.box-chap')?.innerText.trim() || 'Không có nội dung';
  54. resolve(`\n\n------------\n\n${title}\n\n${content}`);
  55. },
  56. onerror: () => resolve(`\n\n[Không ti được chương: ${url}]`)
  57. });
  58. });
  59. }
  60.  
  61. startBtn.addEventListener('click', async () => {
  62. startBtn.disabled = true;
  63. startBtn.textContent = 'Đang tải...';
  64.  
  65. // Chỉ lấy từ khu vực danh sách chương
  66. const chapterSection = [...document.querySelectorAll('.story-detail > .list-chapter')];
  67. let links = [];
  68.  
  69. if (chapterSection.length > 0) {
  70. links = [...chapterSection[0].querySelectorAll('a')].map(a => a.href);
  71. } else {
  72. // Dự phòng: lấy tất cả link từ vùng content chính chứa "chuong-"
  73. links = [...document.querySelectorAll('a')]
  74. .filter(a => a.href.includes('/chuong-') && a.closest('.list-chapter'));
  75. }
  76.  
  77. // Sắp xếp theo số chương
  78. links = links
  79. .filter((v, i, a) => a.indexOf(v) === i) // remove duplicates
  80. .sort((a, b) => {
  81. const n1 = parseInt(a.match(/chuong-(\d+)/)?.[1] || 0);
  82. const n2 = parseInt(b.match(/chuong-(\d+)/)?.[1] || 0);
  83. return n1 - n2;
  84. });
  85.  
  86. if (links.length === 0) {
  87. textarea.value = 'Không tìm thấy danh sách chương!';
  88. startBtn.disabled = false;
  89. startBtn.textContent = 'Bắt đầu tải';
  90. return;
  91. }
  92.  
  93. for (const url of links) {
  94. textarea.value += `\nĐang ti: ${url}`;
  95. const data = await fetchChapter(url);
  96. textarea.value += data;
  97. await sleep(1500);
  98. }
  99.  
  100. startBtn.textContent = 'Đã xong!';
  101. startBtn.disabled = false;
  102. });
  103.  
  104. copyBtn.addEventListener('click', () => {
  105. textarea.select();
  106. document.execCommand('copy');
  107. alert('Đã sao chép nội dung vào clipboard!');
  108. });
  109. })();