// ==UserScript== // @name 弟子小说下载助手 // @namespace http://tampermonkey.net/ // @version 2025.3.7 // @description 自动下载小说内容(带进度条版) // @author Your Name // @match *://www.dizishu.cc/b/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_setValue // @grant GM_getValue // @connect dizishu.cc // ==/UserScript== (function() { 'use strict'; const USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ]; // 进度条容器 const progressBar = document.createElement('div'); progressBar.style = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.2); z-index: 99999; display: none; `; const progressText = document.createElement('div'); progressText.style.marginBottom = '10px'; const barContainer = document.createElement('div'); barContainer.style = ` width: 300px; height: 20px; background: #eee; border-radius: 10px; overflow: hidden; `; const bar = document.createElement('div'); bar.style = ` width: 0%; height: 100%; background: #4CAF50; transition: width 0.3s ease; `; barContainer.appendChild(bar); progressBar.appendChild(progressText); progressBar.appendChild(barContainer); document.body.appendChild(progressBar); function getRandomUserAgent() { return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]; } function decodeContent(buffer) { const encodings = ['utf-8', 'gbk', 'gb2312', 'big5', 'gb18030']; for (let enc of encodings) { try { const decoder = new TextDecoder(enc, {fatal: true}); return decoder.decode(buffer); } catch(e) {} } return new TextDecoder('utf-8', {fatal: false}).decode(buffer); } async function fetchChapter(url, index) { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: url, headers: { "User-Agent": getRandomUserAgent(), "Referer": window.location.href }, responseType: "arraybuffer", onload: function(res) { try { const html = decodeContent(res.response); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); // 修改后的内容选择器 const title = doc.querySelector('.chaptername')?.textContent.trim(); const txtElement = doc.querySelector('#txt'); // 获取原始HTML内容处理换行 let content = txtElement?.innerHTML || ''; // 内容清洗 const cleaned = content .replace(/]*?href\s*=\s*["']\s*javascript:posterror\(\);\s*["'][^>]*>.*?<\/a>/gis, '') // 新增:去除指定锚标签 .replace(/『如果章节错误.*?举报』/gs, '') .replace(//gi, '\n') .replace(/<\/p>|
/gi, '\n') .replace(/ /g, ' ') .replace(/[ \t\u3000]{2,}/g, ' ') .replace(/\n{3,}/g, '\n\n') .trim(); resolve({index, title, content: cleaned}); } catch(e) { resolve({index, error: true}); } }, onerror: () => resolve({index, error: true}) }); }); } async function startDownload() { // 显示进度条 progressBar.style.display = 'block'; bar.style.width = '0%'; progressText.textContent = '准备中...'; try { // 获取书籍信息 const bookName = document.querySelector('h1').textContent.trim(); // 获取第二个ul下的li a const bookChapterList = document.querySelector('.book-chapter-list'); const secondUl = bookChapterList.querySelectorAll('ul')[1]; const chapterLinks = Array.from(secondUl.querySelectorAll('li a')); // 创建章节队列 const chapters = chapterLinks.map((a, index) => ({ url: a.href, index: index + 1, completed: false })); // 并发控制 const parallel = 5; // 降低并发数更稳定 注:只能往小了调,服务器会吃不消 const total = chapters.length; let completed = 0; const results = []; for (let i = 0; i < total; i += parallel) { const batch = chapters.slice(i, i + parallel); const promises = batch.map(c => fetchChapter(c.url, c.index) .then(res => { completed++; // 更新进度 const progress = Math.round((completed / total) * 100); bar.style.width = `${progress}%`; progressText.textContent = `下载中 ${completed}/${total} (${progress}%)`; return res; }) ); const batchResults = await Promise.all(promises); results.push(...batchResults); await new Promise(r => setTimeout(r, 500)); // 增加间隔 } // 处理结果 const validChapters = results .filter(r => !r.error && r.title) .sort((a, b) => a.index - b.index); // 生成内容 const content = validChapters .map(r => `${r.title}\n\n${r.content}\n\n`) .join(''); // 创建下载 const blob = new Blob([content], {type: 'text/plain;charset=utf-8'}); const url = URL.createObjectURL(blob); GM_download({ url: url, name: `${bookName}.txt`, saveAs: true }); progressText.textContent = '下载完成!'; setTimeout(() => progressBar.style.display = 'none', 2000); } catch (e) { progressText.textContent = '下载失败,请刷新重试'; console.error(e); } } // 添加下载按钮 function addButton() { const btn = document.createElement('button'); btn.style = ` padding: 10px 20px 10px 20px; z-index: 9999; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; `; btn.textContent = '下载小说'; btn.onclick = startDownload; // 假设这里有一个btn元素,将下载按钮添加到它下面 document.querySelector('.btn').appendChild(btn); } setTimeout(addButton, 2000); })();