NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name 起点
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 下载起点网站免费小说
// @author twjx
// @match https://www.qidian.com/*
// @match https://dushi.bookresource.qq.com/*
// @match https://www.qdmm.com/*
// @license MIT
// @copyright 2025 twjx
// @icon https://ts2.tc.mm.bing.net/th?id=ODLS.1a7dd876-88a9-4184-96ec-981f87a20b81&w=32&h=32&qlt=90&pcl=fffffc&o=6&pid=1.2
// @grant none
// @run-at document-start
// ==/UserScript==
class add{
qidian(document,qidian){
var btn=document.createElement('a')
document.querySelector('.book-info-top').appendChild(btn)
btn.innerText='下载全文'
btn.className='blue-btn-detail'
btn.style.width='88px'
btn.style.cursor='pointer'
btn.onclick=(function(){
new qidian(document)
})
}
}
class qidian{
constructor(document){
this.get(document)
this.ondownload=false
}
get(document){
const volumes= Array.from(document.querySelector('.catalog-all').children,)
.filter(e=>e.children[1].children[0].children[0].children[1].innerText=='免费') //偏门用法,把HTMLCollection数组转化为普通数组
const data = {
all:[],
adddata : function (data){
console.newlog('已获取章节:'+data.chapterName,data)
const bookVolume=this.all.filter(e=>e.chapterList
.filter(e=>e.name==data.chapterName).length==1)[0] //超绝嵌套filter
const bookdata=bookVolume.chapterList
.filter(e=>e.name==data.chapterName)[0]
this.all.filter(e=>e.volumeName==bookVolume.volumeName)[0].chapterContent.push(data)
}
}
volumes.forEach(volume => {
var text1=volume.children[1].children[0].children[0].innerText.replace('免费','').split('·')
data.all.push({
volumeName:text1[0],
chapterNum:text1[1],
chapterContent:[],
chapterList:Array.from(volume.children[2].children,).map(e=>{return {name:e.innerText,url:e.children[0].href}}),
})
})
this.data=data
this.init(document)
}
async init(document){
this.data.all.forEach(async(volume) => {
await volume.chapterList.forEach(async(chapter) => {
await this.getBook(chapter.url)
await this.sleep(300)
if(this.data.all[this.data.all.length-1].chapterList.length==this.data.all[this.data.all.length-1].chapterContent.length && this.ondownload==false){
this.download(document)
this.ondownload==true
}
})
console.newlog('已获取卷:'+volume.volumeName)
//if(this.data.all.map(e=>e.volumeName).indexOf(volume.volumeName)==this.data.all.length){}
await this.sleep(500)
})
}
getBook(url) {
return new Promise(((resolve, reject) => {//异步 更好监听 load
const xml = new XMLHttpRequest();
xml.open('GET', url, true);
xml.onload = () => {
if (xml.status !== 200 || !xml.responseText) {
reject(new Error("请求失败或响应为空"));
return;
}
try {
const doc = new DOMParser().parseFromString(xml.responseText, "text/html");
const ele = doc.querySelector('.chapter-wrapper div div');
const data = {
bookName: ele.children[1].children[0].innerText,
author: ele.children[1].children[1].innerText,
chapterName: ele.children[0].innerText,
word: ele.children[1].children[2].textContent.split(' ')[1], // 去除 网页图样所需的123数字
date: ele.children[1].children[3].innerText,
content: ele.children[2].innerText
};
this.data.adddata(data);
resolve(data);
} catch (e) {
reject(e);
}
};
xml.onerror = () => {
reject(new Error("请求失败"));
};
xml.send();
}).bind(this));
}
async download(document){
this.downdata=document.querySelector('#bookName').textContent+'\n'+document.querySelector('.author').textContent
+'\n标签:'+document.querySelector('.book-attribute').textContent.replaceAll(' ','')
+'\n简介:'+document.querySelector('.intro').textContent
+'\n数据:'+document.querySelector('.count').textContent
this.data.all.forEach(volume => {
this.downdata+=volume.volumeName+'·'+volume.chapterNum+'\n'
volume.chapterContent.forEach(chapter => {
this.downdata+=chapter.chapterName+' 字数:'+chapter.word+' 更新日期:'+chapter.date+'\n'+chapter.content.replaceAll(' ','\n')+'\n'
})
})
await this.sleep(1000)
var a=document.createElement('a')
a.href='data:text/plain;charset=utf-8,'+encodeURIComponent(this.downdata)
a.download='起点小说--'+document.querySelector('#bookName').textContent+'.txt'
a.click()
console.newlog('下载完成')
}
async sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
window.console.newlog=console.log
window.onload=()=>{
new add().qidian(document,qidian)}