Tải toàn bộ chương từ danh sách chương (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 chương (Tangthuvien)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  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. // UI
  15. const textarea = document.createElement('textarea');
  16. textarea.style.cssText = 'width:100%; height:300px; margin-bottom:10px; white-space: pre-wrap;';
  17.  
  18. const container = document.createElement('div');
  19. container.style.cssText = `
  20. position: fixed; top: 10%; right: 10px; z-index: 9999;
  21. width: 400px; background: white; border: 1px solid #ccc;
  22. padding: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.3);
  23. `;
  24.  
  25. const startBtn = document.createElement('button');
  26. startBtn.textContent = 'Bắt đầu tải';
  27. startBtn.style.cssText = 'width: 100%; padding: 8px; margin-bottom: 10px;';
  28.  
  29. const copyBtn = document.createElement('button');
  30. copyBtn.textContent = 'Sao chép';
  31. copyBtn.style.cssText = 'width: 100%; padding: 8px;';
  32.  
  33. container.appendChild(textarea);
  34. container.appendChild(startBtn);
  35. container.appendChild(copyBtn);
  36. document.body.appendChild(container);
  37.  
  38. function sleep(ms) {
  39. return new Promise(resolve => setTimeout(resolve, ms));
  40. }
  41.  
  42. function fetchChapter(url) {
  43. return new Promise((resolve) => {
  44. GM_xmlhttpRequest({
  45. method: "GET",
  46. url: url,
  47. onload: function (response) {
  48. const parser = new DOMParser();
  49. const doc = parser.parseFromString(response.responseText, 'text/html');
  50. const title = doc.querySelector('h2')?.innerText.trim() || 'Không tiêu đề';
  51. const content = doc.querySelector('.box-chap')?.innerText.trim() || 'Không có nội dung';
  52. resolve(`\n\n------------\n\n${title}\n\n${content}`);
  53. },
  54. onerror: () => resolve(`\n\n[Không ti được chương: ${url}]`)
  55. });
  56. });
  57. }
  58.  
  59. startBtn.addEventListener('click', async () => {
  60. startBtn.disabled = true;
  61. startBtn.textContent = 'Đang tải...';
  62.  
  63. // ✅ LẤY LINK CHƯƠNG TỪ DANH SÁCH (id="chapters")
  64. let links = [...document.querySelectorAll('#chapters a')]
  65. .map(a => a.href)
  66. .filter((v, i, a) => a.indexOf(v) === i); // bỏ trùng
  67.  
  68. // Sắp xếp theo số chương
  69. links = links.sort((a, b) => {
  70. const n1 = parseInt(a.match(/chuong-(\d+)/)?.[1] || 0);
  71. const n2 = parseInt(b.match(/chuong-(\d+)/)?.[1] || 0);
  72. return n1 - n2;
  73. });
  74.  
  75. if (links.length === 0) {
  76. textarea.value = 'Không tìm thấy danh sách chương!';
  77. startBtn.disabled = false;
  78. startBtn.textContent = 'Bắt đầu tải';
  79. return;
  80. }
  81.  
  82. for (const url of links) {
  83. textarea.value += `\nĐang ti: ${url}`;
  84. const data = await fetchChapter(url);
  85. textarea.value += data;
  86. await sleep(1500); // tránh bị chặn
  87. }
  88.  
  89. startBtn.textContent = 'Đã xong!';
  90. startBtn.disabled = false;
  91. });
  92.  
  93. copyBtn.addEventListener('click', () => {
  94. textarea.select();
  95. document.execCommand('copy');
  96. alert('Đã sao chép nội dung vào clipboard!');
  97. });
  98. })();