轻小说文库下载

生成分卷和全本ePub文档

目前為 2023-07-12 提交的版本,檢視 最新版本

// ==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(/ /, '&nbsp;');
        txtTmp = txtTmp.replaceAll(/(c1ef6520).*(1a5219e9b2b0)/,
            function (match) {
                return $('<div></div>').html(match.replaceAll(/(&nbsp;)/, ' ').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}">&nbsp;&nbsp;全本文本下载(${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}">&nbsp;&nbsp;文本下载(${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...
})();