Greasy Fork is available in English.
纯小白请勿下载,第一版主网下载器,因为网址并不固定,所以不做域名匹配
// ==UserScript==
// @name Diyibanzhu Downloader
// @namespace http://tampermonkey.net/
// @version 4.0.1
// @supportURL https://github.com/LanluZ/Diyibanzhu-Download
// @homepageURL https://github.com/LanluZ/Diyibanzhu-Download
// @description 纯小白请勿下载,第一版主网下载器,因为网址并不固定,所以不做域名匹配
// @author LanluZ
// @match http://*/*
// @match https://*/*
// @grant unsafeWindow
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @license MIT
// @require https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js
// @require
// ==/UserScript==
class DiyibanzhuDownloader {
constructor() {
this.title = '';
this.info = '';
this.pdfOptions = {
margin: 1,
image: { type: 'jpeg', quality: 0.95 }, // 使用JPEG格式,稍微降低质量以提高速度
enableLinks: false, // 禁用链接以提高速度
html2canvas: {
scale: 2, // 提高清晰度
useCORS: true, // 允许跨域图片
logging: false, // 禁用日志以提高性能
letterRendering: true
},
jsPDF: {
unit: 'pt',
format: 'a4',
compress: true // 启用压缩以减小文件大小
}
};
}
// 初始化页面
init() {
this.title = this.getTitle();
this.info = this.getInfo();
if (this.existHome()) {
this.layDownloadButton();
this.laySearchButton();
this.layCheckbox();
}
if (this.existContent()) {
this.layCopyContentButton();
}
}
// 发送HTTP请求
sendRequest(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "document";
xhr.onload = () => xhr.status === 200 ? resolve(xhr.response) : reject(new Error(`HTTP ${xhr.status}`));
xhr.onerror = () => reject(new Error('Network Error'));
xhr.send();
});
}
// 下载处理
async downloadButtonClicked() {
try {
const catalogueInfoList = this.getCatalogueInfo(document);
const checkboxList = document.getElementsByClassName("downloadCheckbox");
for (let i = 0; i < catalogueInfoList.length; i++) {
if (!checkboxList[i].checked) continue;
console.log(`开始处理章节: ${catalogueInfoList[i].text}`);
try {
const catalogPage = await this.sendRequest(catalogueInfoList[i].href);
const contentInfoList = this.getContentInfo(catalogPage);
for (const contentInfo of contentInfoList) {
try {
console.log(`> 开始下载: ${catalogueInfoList[i].text}-${contentInfo.text}`);
const contentPage = await this.sendRequest(contentInfo.href);
// 预处理文档
const cleanedDoc = this.removeElement(contentPage, "neirong");
console.log(`>> 解析完成: ${catalogueInfoList[i].text}-${contentInfo.text}`);
// 配置PDF选项
const options = {
...this.pdfOptions,
filename: `${this.title},${catalogueInfoList[i].text},${contentInfo.text}.pdf`
};
// 异步生成PDF
await this.generatePDF(cleanedDoc.body, options);
console.log(`>>> PDF生成完成: ${options.filename}`);
} catch (error) {
console.error(`下载内容页失败: ${contentInfo.text}`, error);
}
}
} catch (error) {
console.error(`下载目录页失败: ${catalogueInfoList[i].text}`, error);
}
}
} catch (error) {
console.error('下载过程出错:', error);
}
}
// 生成PDF
async generatePDF(element, options) {
return html2pdf().set(options).from(element).save();
}
// 搜索功能
searchButtonClicked() {
const encodedTitle = encodeURIComponent(this.title);
window.open(`https://www.google.com/search?q=${encodedTitle}`);
}
// 复制内容
copyContentButtonClicked() {
const div = document.getElementById('nr1');
const text = div.innerText || div.textContent;
navigator.clipboard.writeText(text)
.then(() => console.log('文本已复制'))
.catch(err => console.error('复制失败:', err));
}
// 清理文档元素
removeElement(document, className) {
try {
// 寻找目标标签
const aimTag = document.querySelector('.' + className);
if (!aimTag) {
// 如果没有找到目标标签,尝试查找 nr1 元素
const nr1Tag = document.getElementById('nr1');
if (nr1Tag) {
return document;
}
console.log("未找到目标标签");
return document;
}
// 寻找目标标签之前标签
let prevTags = [];
let currentNode = aimTag;
while (currentNode.previousElementSibling || currentNode.parentElement) {
if (currentNode.previousElementSibling) {
currentNode = currentNode.previousElementSibling;
prevTags.unshift(currentNode);
} else if (currentNode.parentElement) {
currentNode = currentNode.parentElement;
}
}
// 寻找目标标签之后标签
let nextTags = [];
currentNode = aimTag;
while (currentNode.nextElementSibling || currentNode.parentElement) {
if (currentNode.nextElementSibling) {
currentNode = currentNode.nextElementSibling;
nextTags.push(currentNode);
} else if (currentNode.parentElement) {
currentNode = currentNode.parentElement;
}
}
// 寻找目标标签父标签
let parentTags = [];
currentNode = aimTag;
while (currentNode.parentElement) {
parentTags.push(currentNode.parentElement);
currentNode = currentNode.parentElement;
}
// 删除前标签中非父标签和非head标签
for (let i = 0; i < prevTags.length; i++) {
if (!parentTags.includes(prevTags[i]) && !prevTags[i].classList.contains('head')) {
prevTags[i].remove();
}
}
// 删除后标签
for (let i = 0; i < nextTags.length; i++) {
nextTags[i].remove();
}
} catch (e) {
console.error("解析错误:", e);
}
return document;
}
//获取指定内容页章节标题链接
getContentInfo(contentDocument) {
const result = [];
const currentUrl = contentDocument.URL;
// 总是添加当前页面作为第一页
result.push({
href: currentUrl,
text: '[1]'
});
// 检查是否有分页
const catalogueList = contentDocument.getElementsByClassName("chapterPages")[0];
if (!catalogueList) {
return result;
}
// 获取分页链接
const pageLinks = catalogueList.getElementsByTagName("a");
for (let i = 0; i < pageLinks.length; i++) {
// 跳过当前页面的重复链接
if (pageLinks[i].href !== currentUrl) {
result.push({
href: pageLinks[i].href,
text: `[${i + 2}]` // 从[2]开始编号
});
}
}
return result;
}
// 获取目录信息
getCatalogueInfo(catalogueDocument) {
const catalogueList = catalogueDocument.getElementsByClassName("list")[1];
return Array.from(catalogueList.getElementsByTagName("li")).map(li => {
const a = li.getElementsByTagName("a")[0];
return {
href: a.href,
text: a.innerText
};
});
}
// 检查是否为主页
existHome() {
try {
return document.getElementsByClassName("read start")[0].innerHTML === "从头开始阅读";
} catch (e) {
return false;
}
}
// 检查是否为内容页
existContent() {
return !!document.getElementById("nr1");
}
// 获取标题
getTitle() {
return document.getElementsByTagName("h1")[0].innerHTML;
}
// 获取信息
getInfo() {
return document.getElementsByClassName("info")[0].innerHTML.replace(/<br>/g, "");
}
// UI相关方法
layDownloadButton() {
this.createButton("下载", () => this.downloadButtonClicked());
}
laySearchButton() {
this.createButton("搜索", () => this.searchButtonClicked());
}
layCopyContentButton() {
const ftNode = document.getElementsByClassName("page-title")[0];
const button = document.createElement("div");
button.innerHTML = "<tr><td style='width: 50px'><button>复制内容</button></td></tr>";
button.onclick = () => this.copyContentButtonClicked();
ftNode.appendChild(button);
const nr1 = document.getElementById("nr1");
if (nr1) nr1.style.userSelect = "text";
}
createButton(text, onClick) {
const ft = document.getElementsByClassName("ft")[0];
if (!ft) return;
const button = document.createElement("div");
button.innerHTML = `<tr><td style='width: 50px'><a class='read start'>${text}</a></td></tr>`;
button.onclick = onClick;
ft.childNodes[1].childNodes[1].appendChild(button);
}
layCheckbox() {
// 使用与getCatalogueInfo相同的目录列表
const catalogueList = document.getElementsByClassName("list")[1];
if (!catalogueList) {
console.log("未找到目录列表");
return;
}
const items = catalogueList.getElementsByTagName("li");
Array.from(items).forEach(item => {
const checkbox = document.createElement("input");
checkbox.className = "downloadCheckbox";
checkbox.type = "checkbox";
item.insertBefore(checkbox, item.firstChild);
});
}
}
// 初始化脚本
(function() {
'use strict';
const downloader = new DiyibanzhuDownloader();
downloader.init();
})();