弟子小说下载助手
// ==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(/<a\s[^>]*?href\s*=\s*["']\s*javascript:posterror\(\);\s*["'][^>]*>.*?<\/a>/gis, '') // 新增:去除指定锚标签
.replace(/『如果章节错误.*?举报』/gs, '')
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>|<div>/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);
})();