📚【小说下载神器📥,适配PC+移动端
/**
* @Author: xhg
* @Date: 2025-02-04 13:45:10
* @Last Modified by: xhg
* @Last Modified time: 2025-02-04 16:24:15
*/
'use strict';
// ==UserScript==
// @name 📚【小说下载神器📥,适配PC+移动端
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 自动下载并合并小说章节内容
// @author xhg
// @match https://www.deqixs.com/xiaoshuo/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @connect deqixs.com
// ==/UserScript==
(function() {
'use strict';
// 新增:检查当前是否在TXT目录页
if (!location.href.includes('txt.html')) {
// 查找TXT下载链接
const txtLink = document.querySelector('h2.op.bb a[href*="txt.html"]');
if (txtLink) {
// 自动跳转到TXT目录页
window.location.href = new URL(txtLink.href, location.href).href;
return; // 终止当前脚本执行,等待跳转后重新执行
}
else {
console.log('未找到TXT下载链接!');
return;
}
}
// 创建下载按钮(调整尺寸)
const btn = document.createElement('button');
btn.innerHTML = '📚 合并下载';
btn.style.padding = '8px 16px'; // 缩小按钮尺寸
btn.style.fontSize = '14px'; // 调整字体大小
btn.style.position = 'fixed';
btn.style.right = '20px';
btn.style.top = '20px';
btn.style.zIndex = 9999;
btn.style.backgroundColor = '#4CAF50';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '5px';
btn.style.cursor = 'pointer';
btn.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.gap = '8px';
btn.addEventListener('mouseover', () => btn.style.backgroundColor = '#45a049');
btn.addEventListener('mouseout', () => btn.style.backgroundColor = '#4CAF50');
document.body.appendChild(btn);
btn.addEventListener('click', async () => {
console.log('下载按钮被点击');
try {
// 增强标题处理(处理多种字数格式)
const rawTitle = document.querySelector('h1').textContent.trim();
const novelTitle = rawTitle
.replace(/^[\d.]+万字\s*/, '') // 支持小数格式(如"123.4万字")
.replace(/\s+/g, '_')
.replace(/[\\/:*?"<>|]/g, ''); // 去除非法文件名字符
console.log('处理后的标题:', novelTitle);
// 获取所有章节链接
const links = Array.from(document.querySelectorAll('#list ul li a'))
.map(a => ({
url: new URL(a.href, location.href).href,
title: a.textContent
}));
console.log('找到章节链接:', links.length, '个'); // 调试信息
// 创建进度容器(优化样式)
const progressContainer = document.createElement('div');
progressContainer.style.position = 'fixed';
progressContainer.style.right = '20px';
progressContainer.style.top = '60px';
progressContainer.style.background = 'rgba(255,255,255,0.9)';
progressContainer.style.padding = '10px';
progressContainer.style.borderRadius = '8px';
progressContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
// 进度条元素
const progressBar = document.createElement('div');
progressBar.style.width = '200px';
progressBar.style.height = '8px';
progressBar.style.background = '#eee';
progressBar.style.borderRadius = '4px';
progressBar.style.marginBottom = '8px';
// 进度填充
const progressFill = document.createElement('div');
progressFill.style.width = '0%';
progressFill.style.height = '100%';
progressFill.style.background = '#4CAF50';
progressFill.style.borderRadius = '4px';
progressFill.style.transition = 'width 0.3s ease';
progressBar.appendChild(progressFill);
// 进度文本
const progressText = document.createElement('div');
progressText.style.textAlign = 'center';
progressText.style.color = '#666';
progressText.style.fontSize = '12px';
progressContainer.appendChild(progressBar);
progressContainer.appendChild(progressText);
document.body.appendChild(progressContainer);
let totalChapters = 0;
let downloadedChapters = 0;
// 预解析所有章节数
links.forEach(link => {
const match = link.title.match(/(\d+)-(\d+)章/);
if (match) {
const start = parseInt(match[1]);
const end = parseInt(match[2]);
if (start > end) { // 处理异常数据
console.warn('异常章节范围:', link.title);
totalChapters += 1;
} else {
totalChapters += end - start + 1;
}
} else {
totalChapters += 1;
}
});
// 重置进度
let realDownloaded = 0; // 真实下载计数
let simulatedProgress = 0; // 模拟进度计数
progressText.textContent = `初始化中... (总${totalChapters}章)`;
let fullContent = '';
for (const [index, link] of links.entries()) {
try {
// 解析当前链接包含的章节数
const match = link.title.match(/(\d+)-(\d+)章/);
const chaptersInLink = match ?
parseInt(match[2]) - parseInt(match[1]) + 1 : 1;
// 新的更新方法
const updateProgress = () => {
const totalProgress = realDownloaded + simulatedProgress;
const percent = Math.min((totalProgress / totalChapters * 100), 100).toFixed(1);
progressFill.style.width = `${percent}%`;
progressText.textContent = `下载进度:${percent}% (${totalProgress}/${totalChapters}章)`;
};
// 修改后的模拟进度
const simulateProgress = (chapters) => {
let current = 0;
const interval = setInterval(() => {
if (current < chapters && realDownloaded === 0) {
current++;
simulatedProgress = current;
updateProgress();
} else {
clearInterval(interval);
simulatedProgress = 0; // 重置模拟进度
updateProgress();
}
}, 150);
};
simulateProgress(chaptersInLink); // 开始模拟进度
// 修改后的下载处理
const content = await fetchText(link.url);
realDownloaded += chaptersInLink; // 记录真实进度
updateProgress();
fullContent += `\n\n${link.title}\n\n${content}`;
} catch (err) {
console.error('下载失败:', err);
alert(`下载失败: ${link.title}`);
return;
}
}
// 下载完成提示
progressContainer.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
✅ 下载完成!
<span style="font-size: 12px; color: #666">(${links.length}章已合并)</span>
</div>
`;
// 3秒后自动消失
setTimeout(() => {
document.body.removeChild(progressContainer);
}, 3000);
// 生成合并文件(修改文件名)
const blob = new Blob([fullContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
GM_download({
url: url,
name: `${novelTitle}_精校版.txt`,
saveAs: true
});
} catch (err) {
console.error('初始化错误:', err); // 更详细的错误日志
alert('初始化失败,请检查控制台日志');
}
});
// 封装异步请求
function fetchText(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'text',
onload: (res) => {
if (res.status === 200) {
resolve(res.responseText);
} else {
reject(new Error(`HTTP ${res.status}`));
}
},
onerror: reject
});
});
}
})();