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)}