您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
生成分卷和全本ePub文档
当前为
// ==UserScript== // @name 轻小说文库下载 // @namespace wenku8Haoa // @version 2.0.2 // @description 生成分卷和全本ePub文档 // @author HaoaW // @match https://www.wenku8.net/* // @match http://www.wenku8.net/* // @connect dl.wenku8.com // @connect picture.wenku8.com // @connect img.wenku8.com // @connect pic.wenku8.com // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js // @require https://cdn.bootcdn.net/ajax/libs/jszip/2.6.1/jszip.js // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js // @icon https://www.wenku8.net/favicon.ico // @grant GM_xmlhttpRequest // ==/UserScript== (function () { 'use strict'; //epub生成;使用addEventListener绑定; function EpubBuilder(e) { let bInfo = { dlDomain: "dl.wenku8.com", aid: article_id,//本书编号 article_id title: document.getElementById("title").childNodes[0].textContent, //标题 creator: document.getElementById('info').innerText,//作者 bookUrl: self.location.href, XHRArr: [],//下载请求;[{start:false,done:false,url:,loadFun:,data:,bookInfo:bInfo}] loadVolume: (xhr) => { xhr.start = true; let navToc = xhr.bookInfo.nav_toc[xhr.VolumeIndex]; GM_xmlhttpRequest({ method: 'GET', url: xhr.url, onload: function (response) { //xhr.VolumeIndex卷号 let chapterIndex = 0; let ImagesIndex = 0; if (response.status == 200) { //https://developer.mozilla.org/zh-CN/docs/Web/Guide/Parsing_and_serializing_XML //下载分卷文本,转换为html let domParser = new DOMParser(); let rspHtml = domParser.parseFromString( `<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"> <head> <meta charset="utf-8"/> <title>${navToc.volumeName}</title> <link href="../Styles/default.css" rel="stylesheet" type="text/css"/> </head> <body><div class="volumetitle"><h2>${navToc.volumeName}</h2></div><br /></body> </html>`, "text/html"); rspHtml.body.innerHTML += response.responseText; //调用自带简转繁 if (currentEncoding != targetEncoding) { translateBody(rspHtml.body); } let removeChild = []; //处理章节、插图 和 contentdp for (let i = 0; i < rspHtml.body.children.length; i++) { let child = rspHtml.body.children[i]; if ("UL" == child.tagName && "contentdp" == child.id) { removeChild.push(child); } //章节 else if ("DIV" == child.tagName && "chaptertitle" == child.className) { chapterIndex++; //章节h3、分卷h2、书名h1(没有做) //<div class="chaptertitle"><div id="chapter_1" name="xxx"><h3>第一章</h3></div></div> let cTitle = child.innerText; let aName = child.firstChild.getAttribute("name"); let divEle = document.createElement("div"); divEle.id = `chapter_${chapterIndex}`; divEle.setAttribute("name", aName); divEle.innerHTML = `<h3>${cTitle}</h3>`; child.innerHTML = divEle.outerHTML; //添加章节导航 navToc.chapterArr.push({ chapterName: cTitle , chapterID: divEle.id , chapterHref: `${navToc.volumeHref}#${divEle.id}` }); } //内容 else if ("DIV" == child.tagName && "chaptercontent" == child.className) { for (let j = 0; j < child.children.length; j++) { let contentChild = child.children[j]; //插图 if ("DIV" == contentChild.tagName && "divimage" == contentChild.className) {//插图 //取得插图下载地址 let imgASrc = contentChild.getAttribute("title"); let imgUrl = new URL(imgASrc); let imgPath = `Images${imgUrl.pathname}`; //在html中加入img标签 let imgEle = document.createElement("img"); imgEle.setAttribute("loading", "lazy"); imgEle.setAttribute("src", `../${imgPath}`); contentChild.innerHTML = imgEle.outerHTML; //记录图片信息作为epub资源 ImagesIndex++; let ImagesID = `${xhr.bookInfo.ImagesFix}_${xhr.VolumeIndex}_${ImagesIndex}`; let images = { path: `${imgPath}`, content: null, id: ImagesID, idName: contentChild.id }; xhr.bookInfo.Images.push(images); //添加图片下载xhr请求 let xhrImg = { start: false, done: false, url: imgASrc, loadFun: xhr.bookInfo.loadImg, images: images, bookInfo: xhr.bookInfo }; xhr.bookInfo.XHRArr.push(xhrImg); xhrImg.loadFun(xhrImg); } } } } removeChild.forEach(c => rspHtml.body.removeChild(c)); //转换html为xhtml let xmlSerializer = new XMLSerializer(); let rspXml = xmlSerializer.serializeToString(rspHtml); let rspXHtml = domParser.parseFromString(rspXml, "application/xhtml+xml"); rspXHtml.firstChild.setAttribute("xmlns:epub", "http://www.idpf.org/2007/ops"); //保存章节内容,作为epub资源 xhr.bookInfo.Text[xhr.VolumeIndex].content = `<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html> ${rspXHtml.firstChild.outerHTML}`; //生成epub,还有资源未下载则只更新生成进度 xhr.done = true; xhr.bookInfo.buildEpub(xhr.bookInfo); } else { //重新下载 xhr.bookInfo.refreshProgress(xhr.bookInfo, `${navToc.volumeName} 下载失败,重新下载;`); xhr.loadFun(xhr); } }, onerror: () => { //重新下载 xhr.bookInfo.refreshProgress(xhr.bookInfo, `${navToc.volumeName} 下载失败,重新下载;`); xhr.loadFun(xhr); } }); },//分卷下载 loadImg: (xhr) => { xhr.start = true; GM_xmlhttpRequest({ method: 'GET', url: xhr.url, responseType: "arraybuffer", onload: function (response) { if (response.status == 200) { xhr.images.content = response.response; xhr.done = true; xhr.bookInfo.buildEpub(xhr.bookInfo); } else { //重新下载 xhr.bookInfo.refreshProgress(xhr.bookInfo, `${xhr.images.idName} 下载失败,重新下载;`); xhr.loadFun(xhr); } }, onerror: () => { //重新下载 xhr.bookInfo.refreshProgress(xhr.bookInfo, `${xhr.images.idName} 下载失败,重新下载;`); xhr.loadFun(xhr); } }); },//图片下载 nav_toc: [],//导航菜单,第一层分卷,第二层章节{volumeName:,volumeID:,volumeHref:,chapterArr:[{chapterName:,chapterID:,chapterHref:}]} VolumeFix: "Volume",//分卷文件、ID前缀 Text: [],//下载后生成的XHTML;{path:`Text/${volumeHref}`,content:} ImagesFix: "Img",//图片文件、ID前缀 Images: [],//下载的图片;{path:`Images/${url.pathname}`,content:} buildEpub: (info) => { if (info.XHRArr.every(e => e.done)) { info.refreshProgress(info, `开始生成ePub;`); let zip = new JSZip(); //epub固定内容 zip.file("mimetype", info.mimetype); zip.file("META-INF/container.xml", info.container_xml); //todo: new DOMParser(); 操作xml let manifest = document.createElement("manifest"); let spine = document.createElement("spine"); //保存css { let item = document.createElement("item"); item.setAttribute("id", info.defaultCSS.id); item.setAttribute("href", info.defaultCSS.path); item.setAttribute("media-type", "text/css"); manifest.appendChild(item); //保存css zip.file(`OEBPS/${info.defaultCSS.path}`, info.defaultCSS.content); } //生成并保存nav.xhtml //<ol><li><a href="Volume_0.xhtml">第一卷</a><ol><li><a href="Volume_0.xhtml#chapter_1">第一章</a></li></ol></li></ol> { //生成nav.xhtml let domParser = new DOMParser(); let navXhtmlDoc = domParser.parseFromString(info.nav_xhtml.content, "application/xhtml+xml"); let tocEle = navXhtmlDoc.getElementById("toc"); let bOlEle = navXhtmlDoc.createElement("ol"); for (let t of info.nav_toc) { //分卷 let vAEle = navXhtmlDoc.createElement("a"); vAEle.href = t.volumeHref; vAEle.innerText = t.volumeName; let vLiEle = navXhtmlDoc.createElement("li"); vLiEle.appendChild(vAEle); if (t.chapterArr && 0 < t.chapterArr.length) { //分卷的章节 let vOlEle = navXhtmlDoc.createElement("ol"); for (let c of t.chapterArr) { let cAEle = navXhtmlDoc.createElement("a"); cAEle.href = c.chapterHref; cAEle.innerText = c.chapterName; let cLiEle = navXhtmlDoc.createElement("li"); cLiEle.appendChild(cAEle); vOlEle.appendChild(cLiEle); } vLiEle.appendChild(vOlEle); } bOlEle.appendChild(vLiEle); } tocEle.appendChild(bOlEle); let nav_xhtml = `<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html> ${navXhtmlDoc.firstChild.outerHTML}`; //保存nav.xhtml信息到content.opf //manifest节点 let item = document.createElement("item"); item.setAttribute("id", info.nav_xhtml.id); item.setAttribute("href", info.nav_xhtml.path); item.setAttribute("media-type", "application/xhtml+xml"); item.setAttribute("properties", "nav"); manifest.appendChild(item); //spine节点 let itemref = document.createElement("itemref"); itemref.setAttribute("idref", info.nav_xhtml.id); itemref.setAttribute("linear", "no"); spine.appendChild(itemref); //保存nav.xhtml zip.file(`OEBPS/${info.nav_xhtml.path}`, nav_xhtml); } //保存分卷内容 for (let t of info.Text) { let item = document.createElement("item"); item.setAttribute("media-type", "application/xhtml+xml"); item.setAttribute("href", t.path); item.setAttribute("id", t.id); manifest.appendChild(item); let itemref = document.createElement("itemref"); itemref.setAttribute("idref", t.id); spine.appendChild(itemref); zip.file(`OEBPS/${t.path}`, t.content); } //保存图片 for (let t of info.Images) { let itemImg = document.createElement("item"); //media-type暂固定jpeg itemImg.setAttribute("media-type", "image/jpeg"); itemImg.setAttribute("href", t.path); itemImg.setAttribute("id", t.id); manifest.appendChild(itemImg); zip.file(`OEBPS/${t.path}`, t.content, { binary: true }); } //生成书籍信息 let uuid = self.crypto.randomUUID(); let createTime = new Date(); let content_opf = `<?xml version="1.0" encoding="utf-8"?> <package version="3.0" unique-identifier="BookId" xmlns="http://www.idpf.org/2007/opf"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> <dc:language>zh-CN</dc:language> <dc:title>${bInfo.title}</dc:title> <meta property="dcterms:modified">${createTime.toISOString()}</meta> <dc:identifier id="BookId">urn:uuid:${uuid}</dc:identifier> <dc:creator>${info.creator}</dc:creator> <!--第一张插图做封面--> ${(info.Images && 0 < info.Images.length) ? `<meta name="cover" content="${info.Images[0].id}" />` : ''} </metadata> <manifest> ${manifest.innerHTML} </manifest> <spine> ${spine.innerHTML} </spine> </package>`; zip.file("OEBPS/content.opf", content_opf); //书名.开始卷-结束卷.epub let epubName = `${bInfo.title}.${info.nav_toc[0].volumeName}`; if (1 < info.nav_toc.length) { epubName = epubName + '-' + info.nav_toc[info.nav_toc.length - 1].volumeName; } saveAs(zip.generate({ type: "blob", mimeType:"application/epub+zip" }), `${epubName}.epub`); info.refreshProgress(info, `ePub生成完成,文件名:${epubName}.epub;`); } else { info.refreshProgress(info); } },//生成epub,如果XHRArr已经都完成了 mimetype: 'application/epub+zip',//epub mimetype 文件内容 container_xml: `<?xml version="1.0" ?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml" /> </rootfiles> </container>`,//epub container.xml 文件内容 nav_xhtml: { content: ` <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="en" xml:lang="en"> <head> <title>ePub NAV</title> <meta charset="utf-8"/> <link href="../Styles/default.css" rel="stylesheet" type="text/css"/> </head> <body epub:type="frontmatter"> <nav epub:type="toc" id="toc" role="doc-toc"> <h2>目录</h2> </nav> </body> </html>` , path: `Text/nav.xhtml` , id: `nav_xhtml_id` },//epub nav.xhtml 文件模板 defaultCSS: { content: ` nav#landmarks { display:none; } nav#page-list { display:none; } ol { list-style-type: none; } .volumetitle , .chaptertitle { text-align: center; } ` , id: "default_css_id" , path: "Styles/default.css" },//epub default.css 样式文件 progressEle: null,//进度、日志 元素;{txt:,img:,err:} refreshProgress: (info, err) => { if (!info.progressEle) { //epub生成进度,下载文本进度:7/7;下载图片进度:77/77;日志:开始生成ePub;ePub生成完成; info.progressEle = {}; info.progressEle.txt = document.createElement("span"); info.progressEle.img = document.createElement("span"); info.progressEle.err = document.createElement("span"); let logDiv = document.createElement('div'); logDiv.appendChild(document.createTextNode("epub生成进度,下载文本进度:")); logDiv.appendChild(info.progressEle.txt); logDiv.appendChild(document.createTextNode(";下载图片进度:")); logDiv.appendChild(info.progressEle.img); logDiv.appendChild(document.createTextNode(";日志:")); logDiv.appendChild(info.progressEle.err); document.body.insertBefore(logDiv, document.getElementById('title')); } //日志 if (err) { info.progressEle.err.innerText = err + info.progressEle.err.innerText; } //文本进度 let txtProgress = info.Text.filter((value, key, arr) => { return value.content; }).length; info.progressEle.txt.innerText = `${txtProgress}/${info.Text.length}`; //图片进度,文本下载完成后才能得到图片总数 if (txtProgress == info.Text.length) { let imgProgress = info.Images.filter((value, key, arr) => { return value.content; }).length; info.progressEle.img.innerText = `${imgProgress}/${info.Images.length}`; } },//显示进度日志 start: (e) => { //全本分卷 let vcssEle = document.getElementsByClassName("vcss"); if ("bookDownload" != e.target.id) { //全本下载 链接的id是bookDownload vcssEle = [e.target.parentElement]; } for (let VolumeIndex = 0; VolumeIndex < vcssEle.length; VolumeIndex++) { let vcss = vcssEle[VolumeIndex]; let vid = vcss.parentElement.nextElementSibling.getElementsByTagName('a')[0].getAttribute('href').split('.')[0];; //let vid = vcss.getAttribute("vid"); let vcssText = vcss.childNodes[0].textContent; let navToc = bInfo.nav_toc[VolumeIndex] = { volumeName: vcssText , volumeID: `${bInfo.VolumeFix}_${VolumeIndex}` , volumeHref: `${bInfo.VolumeFix}_${VolumeIndex}.xhtml` , chapterArr: [] }; bInfo.Text[VolumeIndex] = { path: `Text/${navToc.volumeHref}` , content: "" , id: navToc.volumeID }; //分卷下载链接 let dlink = `https://${bInfo.dlDomain}/pack.php?aid=${bInfo.aid}&vid=${vid}`; let xhr = { start: false, done: false, url: dlink, loadFun: bInfo.loadVolume, VolumeIndex: VolumeIndex, data: { vid: vid, vcssText: vcssText }, bookInfo: bInfo }; bInfo.XHRArr.push(xhr); xhr.loadFun(xhr); } bInfo.buildEpub(bInfo); }//入口,开始下载文件并生成epub; }; bInfo.start(e); }; //替换所有 String.prototype.replaceAll = function (exp, newStr) { return this.replace(new RegExp(exp, "gm"), newStr); }; //标记含有html代码的行 var htmlLine = function (txt) { return 'c1ef6520' + txt + '1a5219e9b2b0'; }; //格式化内容 var contentFormat = function (txt) { //设置章节名样式 let txtTmp = txt.replaceAll(/^ {2}\S+.*$/, function (match) { return htmlLine('<br/><div style="background:#e4e1d8" id="title">' + match.trim() + '</div>'); } ); txtTmp = $('<div></div>').text(txtTmp).html(); txtTmp = txtTmp.replaceAll(/ /, ' '); txtTmp = txtTmp.replaceAll(/(c1ef6520).*(1a5219e9b2b0)/, function (match) { return $('<div></div>').html(match.replaceAll(/( )/, ' ').replace('c1ef6520', '').replace('1a5219e9b2b0', '')).text(); } ); txtTmp = txtTmp.replaceAll(/[\r\n]+/, '<br/>'); return txtTmp; }; //格式化图片 var imgFormat = function (txt) { let imgResultRoot = $('<div></div>'); let imgRoot = $('<div></div>').html(txt); $('.divimage', imgRoot).each(function () { let jqthis = $(this); let imgURL = jqthis.attr('title');//取得图片url let itemEle = $('<div class="divimage"><a target="_blank"><img border="0" class="imagecontent" /></a></div>'); $('a', itemEle).attr('href', imgURL); $('img', itemEle).attr('src', imgURL); imgResultRoot.append(itemEle); }); return imgResultRoot.html(); } //格式化从pack.php下载的带有Html的内容 var htmlFormat = function (htmlStr) { let resultRoot = $('<div></div>'); let strRoot = $('<div></div>').html(htmlStr); $('.chaptertitle', strRoot).each(function () { let jqthis = $(this); let cTitle = jqthis.text();//取得章节名 let itemEle = $('<div style="background-color:silver" id="title"></div>'); itemEle.text(cTitle); resultRoot.append(itemEle); resultRoot.append('<br />'); let nextHtml = jqthis.next().html(); if (-1 != cTitle.indexOf('插图')) { nextHtml = imgFormat(nextHtml); } resultRoot.append(nextHtml); resultRoot.append('<br /><br />'); }); return resultRoot.html(); } //目录或内容页面会声明章节变量。 if ('undefined' == typeof chapter_id || undefined === chapter_id) { } else { //本书编号 article_id //目录页面章节id定义为 '0' if ('0' == chapter_id) {//在章节名之后添加下载链接 //书名 let aname = $('#title').text(); //targetEncoding 1: 繁體中文, 2: 简体中文 let charsetDL = 'utf-8'; let charsetDLAll = 'utf8'; if ('1' == targetEncoding) { charsetDL = 'big5'; charsetDLAll = 'big5'; } //添加全本下载链接 let allDLink = `https://dl.wenku8.com/down.php?type=${charsetDLAll}&id=${article_id}&fname=${aname}`; let allaEle = `<a href="${allDLink}"> 全本文本下载(${charsetDLAll})</a>`; $('#title').append(allaEle); $('.vcss').each(function () { let jqthis = $(this); let vname = jqthis.text(); let nextChapter = jqthis.parent().next().children().first(); let vid = $('a', nextChapter).attr('href').split('.')[0]; //vid = (Number(vid)-1).toString();//部分文章无法使用章节减1来下载,直接使用章节可以下载(下载内容使用-1时章节名包含小说名,不-1不包含小说名) let dlink = `https://dl.wenku8.com/packtxt.php?aid=${article_id}&vid=${vid}&aname=${aname}&vname=${vname}&charset=${charsetDL}`; let aEle = `<a href="${dlink}"> 文本下载(${charsetDL})</a>`; jqthis.append(aEle); }); //添加 ePub下载(全本) let allaEpubEle = document.createElement("a"); allaEpubEle.id = "bookDownload"; allaEpubEle.innerText = " ePub下载(全本)"; allaEpubEle.href = "javascript:void(0);"; document.getElementById('title').append(allaEpubEle); allaEpubEle.addEventListener("click", EpubBuilder); //添加 ePub下载(本卷) for (let v of document.getElementsByClassName("vcss")) { let allaEle = document.createElement("a"); allaEle.href = "javascript:void(0);"; allaEle.innerText = " ePub下载(本卷)"; v.append(allaEle); allaEle.addEventListener("click", EpubBuilder); }; } else { if ('0' != chapter_id) {//内容页面 //如果包含一个内容为 'null'的span则判定为版权限制 if ($('#contentmain span').first().text().trim() == 'null') { //设置下一页和上一页快捷键到目录页 preview_page = next_page = index_page; $('#content').text('正在下载,请稍候...'); //下载带有Html标记的内容 let dlink = 'http://dl.wenku8.com/pack.php?aid=' + article_id + '&vid=' + chapter_id; GM_xmlhttpRequest({ method: 'GET', url: dlink, onload: function (response) { if (response.status == 200) { var formatHtml = htmlFormat(response.responseText); $('#content').html(formatHtml); } else {//html下载可能会失败,使用下载文本的方法来下载 if (-1 != $('#title').text().indexOf('插图')) { $('#content').text('无法获取插图'); } else { dlink = 'http://dl.wenku8.com/packtxt.php?aid=' + article_id + '&vid=' + chapter_id; GM_xmlhttpRequest({ method: 'GET', url: dlink, onload: function (response) { if (response.status == 200) { let formatTxt = contentFormat(response.responseText); $('#content').html(formatTxt); } else { $('#content').text('下载失败'); } }, }); } } }, }); } } } } // Your code here... })();