// ==UserScript==
// @name 启迪小说TXT下载器
// @namespace http://tampermonkey.net/
// @version 1.7
// @description 支持选择章节范围的小说下载器,优化性能警告
// @author GitHub:Dolphin-QvQ
// @match *://www.qidiy.com/book/*/
// @grant GM_download
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/555685/%E5%90%AF%E8%BF%AA%E5%B0%8F%E8%AF%B4TXT%E4%B8%8B%E8%BD%BD%E5%99%A8.user.js
// @updateURL https://update.greasyfork.icu/scripts/555685/%E5%90%AF%E8%BF%AA%E5%B0%8F%E8%AF%B4TXT%E4%B8%8B%E8%BD%BD%E5%99%A8.meta.js
// ==/UserScript==
(function() {
'use strict';
// 配置参数
const CONFIG = {
collectInterval: 100,
downloadInterval: 100
};
let chapterList = [];
let isCollecting = false;
let baseUrl = '';
let bookId = '';
let protocol = 'https:';
// 缓存DOM元素,避免频繁查询
let dom = {
statusDiv: null,
countDiv: null,
progressFill: null,
collectBtn: null,
downloadBtn: null
};
// 初始化基础信息和DOM缓存
function init() {
protocol = window.location.protocol;
const pathMatch = window.location.pathname.match(/\/book\/(\d+)(?:\/|_|\d+\/)/);
if (pathMatch && pathMatch[1]) {
bookId = pathMatch[1];
baseUrl = `${protocol}//www.qidiy.com/book/${bookId}/`;
}
createControlPanel();
// 缓存DOM元素
dom.statusDiv = document.getElementById('downloadStatus');
dom.countDiv = document.getElementById('chapterCount');
dom.progressFill = document.getElementById('progressFill');
dom.collectBtn = document.querySelector('button:first-of-type');
dom.downloadBtn = document.querySelector('button:last-of-type');
}
// 创建控制界面(减少DOM操作次数)
function createControlPanel() {
const panel = document.createElement('div');
panel.style.cssText = `
position: fixed; top: 20px; right: 20px; z-index: 9999;
background: white; padding: 15px; border: 1px solid #ccc;
border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);
max-width: 500px;
`;
// 一次性拼接HTML,减少DOM插入次数
panel.innerHTML = `
小说章节下载器(范围选择)
准备就绪,点击"收集所有章节"开始
已收集章节: 0
`;
document.body.appendChild(panel);
// 绑定间隔输入框事件
document.getElementById('collectInterval').addEventListener('change', e => {
CONFIG.collectInterval = parseInt(e.target.value) || 500;
});
document.getElementById('downloadInterval').addEventListener('change', e => {
CONFIG.downloadInterval = parseInt(e.target.value) || 1000;
});
// 绑定按钮事件
panel.querySelector('button:first-of-type').addEventListener('click', startCollectingChapters);
panel.querySelector('button:last-of-type').addEventListener('click', downloadSelectedRange);
}
// 更新章节选择下拉框(优化DOM操作)
function updateChapterSelectors() {
const startSelect = document.getElementById('startChapter');
const endSelect = document.getElementById('endChapter');
// 先清空再批量添加,减少重排
startSelect.innerHTML = '';
endSelect.innerHTML = '';
const fragment = document.createDocumentFragment(); // 文档片段,减少DOM插入次数
const startOptions = [];
const endOptions = [];
chapterList.forEach((chapter, index) => {
const optionText = `[${index + 1}] ${chapter.title} (${chapter.id})`;
const startOpt = document.createElement('option');
startOpt.value = chapter.id;
startOpt.textContent = optionText;
startOptions.push(startOpt);
const endOpt = document.createElement('option');
endOpt.value = chapter.id;
endOpt.textContent = optionText;
endOptions.push(endOpt);
});
// 批量添加选项
startOptions.forEach(opt => startSelect.appendChild(opt));
endOptions.forEach(opt => endSelect.appendChild(opt));
if (chapterList.length > 0) {
endSelect.selectedIndex = chapterList.length - 1;
startSelect.disabled = false;
endSelect.disabled = false;
dom.downloadBtn.disabled = false;
}
}
// 提取当前页正文章节
function extractCurrentPageChapters(doc) {
const contentTit = Array.from(doc.querySelectorAll('.layout-tit')).find(
el => el.textContent.trim().includes('正文')
);
if (!contentTit) return [];
const sectionBox = contentTit.nextElementSibling;
if (!sectionBox || !sectionBox.classList.contains('section-box')) return [];
const links = sectionBox.querySelectorAll('.section-list a');
const chapters = [];
links.forEach(link => {
const href = link.getAttribute('href');
const idMatch = href.match(/\/book\/\d+\/(\d+)\.html/);
if (idMatch && idMatch[1]) {
chapters.push({ id: idMatch[1], title: link.textContent.trim() });
}
});
return chapters;
}
// 开始收集所有章节
function startCollectingChapters() {
if (isCollecting || !bookId) return;
isCollecting = true;
chapterList = [];
dom.statusDiv.textContent = '开始收集章节...';
dom.collectBtn.disabled = true;
dom.downloadBtn.disabled = true;
fetch(baseUrl)
.then(response => response.ok ? response.text() : Promise.reject(`HTTP错误: ${response.status}`))
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const currentChapters = extractCurrentPageChapters(doc);
chapterList.push(...currentChapters);
updateChapterCount();
const select = doc.querySelector('select[name="pageselect"]');
if (!select) {
dom.statusDiv.textContent = '未找到分页控件,仅收集到当前页章节';
finishCollecting();
return;
}
// 修正分页URL为HTTPS
Array.from(select.options).forEach(option => {
if (option.value) {
option.value = option.value.startsWith('http')
? option.value.replace('http://', 'https://')
: `${protocol}//www.qidiy.com${option.value}`;
}
});
traverseAllPages(select, 0);
})
.catch(error => {
console.error('获取书籍首页失败:', error);
dom.statusDiv.textContent = `获取失败: ${error}`;
isCollecting = false;
dom.collectBtn.disabled = false;
});
}
// 遍历所有分页
function traverseAllPages(select, currentIndex) {
if (!isCollecting) return;
const totalPages = select.options.length;
dom.statusDiv.textContent = `正在收集第 ${currentIndex + 1}/${totalPages} 页章节...`;
if (currentIndex === 0) {
processNextPage(select, currentIndex, totalPages);
return;
}
let pageUrl = select.options[currentIndex].value.replace('http://', 'https://');
fetch(pageUrl)
.then(response => response.ok ? response.text() : Promise.reject(`HTTP错误: ${response.status}`))
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const pageChapters = extractCurrentPageChapters(doc);
// 去重添加
pageChapters.forEach(chapter => {
if (!chapterList.some(c => c.id === chapter.id)) {
chapterList.push(chapter);
}
});
updateChapterCount();
processNextPage(select, currentIndex, totalPages);
doc.body.innerHTML = ''; // 释放内存
})
.catch(error => {
console.error(`加载分页失败:`, error);
if (confirm(`第 ${currentIndex + 1} 页加载失败,是否继续?`)) {
processNextPage(select, currentIndex, totalPages);
} else {
finishCollecting();
}
});
}
// 处理下一页
function processNextPage(select, currentIndex, totalPages) {
if (currentIndex + 1 < totalPages) {
setTimeout(() => traverseAllPages(select, currentIndex + 1), CONFIG.collectInterval);
} else {
finishCollecting();
}
}
// 完成收集
function finishCollecting() {
isCollecting = false;
chapterList.sort((a, b) => parseInt(a.id) - parseInt(b.id));
updateChapterSelectors();
dom.statusDiv.textContent = `章节收集完成!共找到 ${chapterList.length} 个正文章节`;
dom.collectBtn.disabled = false;
}
// 更新章节计数(减少DOM操作)
function updateChapterCount() {
dom.countDiv.textContent = `已收集章节: ${chapterList.length}`;
}
// 提取单页内容
function extractPageContent(doc) {
const titleElem = doc.querySelector('h1.title');
let chapterTitle = titleElem ? titleElem.textContent.trim() : '未知章节';
const contentElem = doc.getElementById('content');
if (!contentElem) return { chapterTitle, content: '', totalPages: 1 };
let totalPages = 1;
const contentHtml = contentElem.innerHTML;
const pageMatch = contentHtml.match(/\(第(\d+)\/(\d+)页\)/i);
if (pageMatch && pageMatch.length === 3) {
totalPages = parseInt(pageMatch[2], 10);
}
let content = contentHtml
.replace(/