// ==UserScript==
// @name 轻小说文库下载
// @namespace wenku8Haoa
// @version 2.1.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';
let hrefUrl = new URL(window.location.href);
const ePubEidterCfgUID = "24A08AE1-E132-458C-9E1D-6C998F16A666";
const ImgLocationFile = "ImgLocation";
//epub生成;使用addEventListener绑定;
function EpubBuilder(e) {
let bInfo = {
Domain: "www.wenku8.net",
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}]
SpanFix: "Txt",//文字ID前缀
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;
let TextIndex = 0;
let Text = xhr.bookInfo.Text[xhr.VolumeIndex];
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>
<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.childNodes.length; j++) {
let contentChild = child.childNodes[j];
//文字
if (Node.TEXT_NODE == contentChild.nodeType && contentChild.textContent != '\n') {
TextIndex++;
let txtSpan = rspHtml.createElement("span");
txtSpan.id = `${xhr.bookInfo.SpanFix}_${xhr.VolumeIndex}_${TextIndex}`;
txtSpan.className = "txtDropEnable";
txtSpan.setAttribute("ondragover", "return false");
child.insertBefore(txtSpan, contentChild);
txtSpan.appendChild(contentChild);
}
//插图
else 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, TextId: Text.id };
//封面候补 第一卷的前两张图,高/宽 > 1
if (0 == xhr.VolumeIndex && 3 > ImagesIndex) {
images.coverImgChk = true;
}
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));
Text.volumeName = navToc.volumeName;
Text.content = rspHtml.body.innerHTML;
//生成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;
if (xhr.images.coverImgChk && (!xhr.bookInfo.Images.find(i => i.coverImg))) {
xhr.images.Blob = new Blob([xhr.images.content], { type: "image/jpeg" });
xhr.images.ObjectURL = URL.createObjectURL(xhr.images.Blob);
let imgEle = new Image();
imgEle.onload = () => {
//高比宽大于1就能做封面
xhr.images.coverImg = (imgEle.naturalHeight / imgEle.naturalWidth > 1);
xhr.done = true;
xhr.bookInfo.buildEpub(xhr.bookInfo);
};
imgEle.src = xhr.images.ObjectURL;
}
else {
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)) {
if (info.ePubEidt && (!info.ePubEidtDone)) {
info.refreshProgress(info, `开始编辑ePub;`);
info.ePubEidterInit(info);
return;
}
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");
//保存插图位置
if (info.ePubEidterCfg.ImgLocation && 0 < info.ePubEidterCfg.ImgLocation.length) {
let cfgJson = JSON.stringify(info.ePubEidterCfg, null, " ");
zip.file("OEBPS/Other/ePubEidterCfg.json", cfgJson);
}
//保存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);
//转换html为xhtml
let domParser = new DOMParser();
let rspHtml = domParser.parseFromString(
`<html>
<head>
<meta charset="utf-8"/>
<title>${t.volumeName}</title>
<link href="../Styles/default.css" rel="stylesheet" type="text/css"/>
</head>
<body></body>
</html>`
, "text/html");
rspHtml.body.innerHTML = t.content;
//添加插图并去除拖放标签
let vLocation = info.ePubEidterImgLocation.filter(i => t.vid == i.vid);
let volumeImgs = info.Images.filter(i => i.TextId == t.id);
let dropEleArr = rspHtml.body.getElementsByClassName("txtDropEnable");
let dropEle;
while ((dropEle = dropEleArr[0])) {
//加载已拖放的图片
let locArr;
let dImg;
if (vLocation
&& (locArr = vLocation.filter(j => j.spanID == dropEle.id))
) {
for (let loc of locArr) {
if (dImg = volumeImgs.find(j => j.idName == loc.imgID)){
let divimage = rspHtml.createElement("div");
divimage.className = "divimage";
let imgEle = rspHtml.createElement("img");
imgEle.setAttribute("loading", "lazy");
imgEle.setAttribute("src", `../${dImg.path}`);
divimage.innerHTML = imgEle.outerHTML;
dropEle.parentNode.insertBefore(divimage, dropEle);
}
}
}
//去除文字span
dropEle.parentNode.insertBefore(dropEle.firstChild, dropEle);
dropEle.parentNode.removeChild(dropEle);
}
//转换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资源
let tContent = `<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
${rspXHtml.firstChild.outerHTML}`;
zip.file(`OEBPS/${t.path}`, tContent);
}
//保存图片
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 coverMeta = '';
if (info.Images.length > 0) {
let coverImg = info.Images.find(i => i.coverImg);
if (!coverImg) {
coverImg = info.Images.find(i => i.coverImgChk);
}
if (coverImg) {
coverMeta = `<meta name="cover" content="${coverImg.id}" />`;
}
}
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>
<!--第一张插图做封面-->
${coverMeta}
</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 ePubEidt = e.target.getAttribute("ePubEidt");
if (ePubEidt && "true" == ePubEidt) {
bInfo.ePubEidt = true;
}
//加载从评论读取的配置
if (bInfo.ePubEidterCfgRef && 0 < bInfo.ePubEidterCfgRef.length) {
for (let cfgRef of bInfo.ePubEidterCfgRef) {
if (ePubEidterCfgUID == cfgRef.UID
&& bInfo.aid == cfgRef.aid
&& cfgRef.ImgLocation
&& 0 < cfgRef.ImgLocation.length
) {
for (let loc of cfgRef.ImgLocation) {
//插图位置记录{vid:,spanID:,imgID:}
if (loc.vid && loc.spanID && loc.imgID) {
if (!bInfo.ePubEidterImgLocation.find(f =>
f.vid == loc.vid
&& f.spanID == loc.spanID
&& f.imgID == loc.imgID
)) {
bInfo.ePubEidterImgLocation.push(loc);
}
}
}
}
}
}
if (bInfo.ePubEidterImgLocation && 0 < bInfo.ePubEidterImgLocation.length) {
bInfo.ePubEidterCfg.ImgLocation = bInfo.ePubEidterImgLocation;
}
//全本分卷
let vcssEle = null;
let DownloadAll = e.target.getAttribute("DownloadAll");
if (DownloadAll && "true" == DownloadAll) {
//全本下载
vcssEle = document.getElementsByClassName("vcss");
}
else {
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
, vid: vid
, volumeID: `${bInfo.VolumeFix}_${VolumeIndex}`
, volumeHref: `${bInfo.VolumeFix}_${VolumeIndex}.xhtml`
, chapterArr: []
};
bInfo.Text[VolumeIndex] = {
path: `Text/${navToc.volumeHref}`
, content: ""
, id: navToc.volumeID
, vid: vid
};
//分卷下载链接
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;
novelTable: null,
ePubEidterCfgRef: ePubEidterCfgRef,//读取到的配置
ePubEidterCfg: {
UID: ePubEidterCfgUID,
aid: article_id,
pathname: hrefUrl.pathname,
ImgLocation: null
},//插图位置配置
ePubEidtImgRegExp: [/img/i, /插图/i, /插圖/i, /\.jpg/i, /\.png/i],//推测插图位置的正则
ePubEidtLink: [],
ePubEidt: false,
ePubEidtDone: false,
ePubEidterHtml: ePubEidterHtml,//编辑器html代码
ePubEidter: null,
ePubEidterImgLocation: [],//插图位置记录{vid:,spanID:,imgID:}
ePubEidterLastVolumeUL:null,
ePubEidterInit: (info) => {
//隐藏目录
let downloadEleArr = document.getElementsByClassName("DownloadAll");
for (let f of downloadEleArr) {
f.style.pointerEvents = "none";
}
info.novelTable = document.body.getElementsByTagName("table")[0];
info.novelTable.style.display = "none";
//加载编辑器、样式
let linkEle = document.createElement("link");
linkEle.type = "text/css";
linkEle.rel = "stylesheet";
linkEle.href = "/themes/wenku8/style.css";
document.head.appendChild(linkEle);
info.ePubEidtLink.push(linkEle);
let divEle = document.createElement("div");
divEle.id = "ePubEidter";
divEle.innerHTML = info.ePubEidterHtml;
info.ePubEidter = divEle;
info.novelTable.parentElement.insertBefore(divEle, info.novelTable);
document.getElementById("EidterBuildBtn").addEventListener("click", info.ePubEidterDoneFun(info));
document.getElementById("EidterImportBtn").addEventListener("click", info.ePubEidterImportCfgFun(info));
document.getElementById("VolumeImg").addEventListener("drop", info.ePubEidterImgDelDropFun(info));
//加载配置内容
if (info.ePubEidterCfg && info.ePubEidterCfg.ImgLocation && 0 < info.ePubEidterCfg.ImgLocation.length) {
let cfgAreaEle = document.getElementById("CfgArea");
cfgAreaEle.value = JSON.stringify(info.ePubEidterCfg, null, " ");
}
//加载分卷列表
let liEleFirst = null;
let VolumeULEle = document.getElementById("VolumeUL");
VolumeULEle.innerHTML = "";
for (let i = 0; i < info.Text.length; i++) {
let text = info.Text[i];
let liEle = document.createElement("li");
VolumeULEle.appendChild(liEle);
let aEle = document.createElement("a");
liEle.appendChild(aEle);
aEle.href = "javascript:void(0);";
aEle.id = text.id;
aEle.innerText = text.volumeName;
liEle.addEventListener("click", info.ePubEidterVolumeULFun(info, text));
if (!liEleFirst) {
liEleFirst = liEle;
}
}
//加载第一卷
if (liEleFirst) {
liEleFirst.click();
}
},//编辑器初始化
ePubEidterDestroyer: (info) => {
info.ePubEidter.parentElement.removeChild(info.ePubEidter);
info.ePubEidtLink.forEach(f => f.parentElement.removeChild(f));
info.novelTable = document.body.getElementsByTagName("table")[0];
info.novelTable.style.display = "";
info = null;
let downloadEleArr = document.getElementsByClassName("DownloadAll");
for (let f of downloadEleArr) {
f.style.pointerEvents = "auto";
}
},//编辑器销毁
ePubEidterDoneFun: (info) => {
return () => {
//生成ePub
info.ePubEidtDone = true;
info.buildEpub(info);
//发送配置
let sendArticleEle = document.getElementById('SendArticle');
if (sendArticleEle.checked && 0 < info.ePubEidterImgLocation.length) {
let cfgObj = Object.assign({}, info.ePubEidterCfg);
//压缩位置
let imgLocJson = JSON.stringify(cfgObj.ImgLocation);
let zip = new JSZip();
zip.file(ImgLocationFile, imgLocJson, {
compression: "DEFLATE",
compressionOptions: {
level: 9
}
});
let imgLocBase64 = zip.generate({ type: "base64", mimeType: "application/zip" });
cfgObj.ImgLocation = null;
cfgObj.ImgLocationBase64 = imgLocBase64;
let cfgJson = JSON.stringify(cfgObj);
let vidSet = new Set();
let vName = [];
for (let loc of info.ePubEidterImgLocation) {
if (!vidSet.has(loc.vid)) {
vidSet.add(loc.vid);
let nToc = info.nav_toc.find(f => loc.vid == f.vid);
if (nToc) {
vName.push(nToc.volumeName);
}
}
}
let pcontent = `包含分卷列表:${vName}
[code]${cfgJson}[/code]`;
let map = new Map();
map.set("ptitle", "ePub插图位置");
map.set("pcontent", pcontent);
let url = `https://${info.Domain}/modules/article/reviews.php?aid=${info.aid}`;
//发送配置
info.ePubEidterSend(info, url, map);
}
let ePubEditerClose = document.getElementById('ePubEditerClose');
if (ePubEditerClose.checked) {
info.ePubEidterDestroyer(info);
}
};
},//点击生成ePub事件
ePubEidterImportCfgFun: (info) => {
return () => {
let cfgAreaEle = document.getElementById("CfgArea");
let impCfg = JSON.parse(cfgAreaEle.value);
if (impCfg
&& impCfg.UID == info.ePubEidterCfg.UID
&& impCfg.aid == info.ePubEidterCfg.aid
&& impCfg.ImgLocation
&& 0 < impCfg.ImgLocation.length
) {
for (let iCfg of impCfg.ImgLocation) {
if (info.ePubEidterImgLocation.find(i =>
i.spanID == iCfg.spanID
&& i.vid == iCfg.vid
&& i.imgID == iCfg.imgID)
) {
continue;
}
else {
info.ePubEidterImgLocation.push(iCfg);
}
}
}
info.ePubEidterCfg.ImgLocation = info.ePubEidterImgLocation;
cfgAreaEle.value = JSON.stringify(info.ePubEidterCfg, null, " ");
if (info.ePubEidterLastVolumeUL) {
info.ePubEidterLastVolumeUL.click();
}
};
},//点击导入配置事件
ePubEidterVolumeULFun: (info, text) => {
return (ev) => {
//最后点击的章节列表,导入配置后刷新
info.ePubEidterLastVolumeUL = ev.target;
//加载文本内容
let VolumeTextEle = document.getElementById("VolumeText");
VolumeTextEle.innerHTML = text.content;
//加载图片列表
let imgEleMap = new Map();
let VolumeImgEle = document.getElementById("VolumeImg");
VolumeImgEle.innerHTML = "";
let volumeImgs = info.Images.filter(i => i.TextId == text.id);
for (let image of volumeImgs) {
if (!image.ObjectURL) {
image.Blob = new Blob([image.content], { type: "image/jpeg" });
image.ObjectURL = URL.createObjectURL(image.Blob);
}
let imgDivEle = document.createElement("div");
imgDivEle.style.float = "left";
imgDivEle.style.textAlign = "center";
imgDivEle.style.height = "155px";
imgDivEle.style.overflow = "hidden";
imgDivEle.style.margin = "0 2px";
VolumeImgEle.appendChild(imgDivEle);
let imgEle = document.createElement("img");
imgEle.setAttribute("imgID", image.idName);
imgEle.setAttribute("loading","lazy");
imgEle.src = image.ObjectURL;
imgEle.height = 127;
//加载用
imgEleMap.set(image.idName, imgEle);
imgDivEle.appendChild(imgEle);
imgDivEle.appendChild(document.createElement("br"));
let imgTextEle = new Text(image.id)
imgDivEle.appendChild(imgTextEle);
//<div style="float: left; text-align: center; height: 155px; overflow: hidden; margin: 0 2px;">
// <img id="Img_160408" src="./160408.jpg" border="0" height="127"><br>
//</div>
}
//推测插图处置
let ImgULEle = document.getElementById("ImgUL");
ImgULEle.innerHTML = "";
//加载已拖放的图片
let vLocation = info.ePubEidterImgLocation.filter(i => text.vid == i.vid);
//拖放处理绑定
let dropEleArr = document.getElementsByClassName("txtDropEnable");
for (let dropEle of dropEleArr) {
dropEle.addEventListener("drop", info.ePubEidterImgDropFun(info, text));
//加载已拖放的图片
let locArr;
let dImgEle;
if (vLocation && (locArr = vLocation.filter(j => j.spanID == dropEle.id))) {
for (let loc of locArr) {
if (dImgEle = imgEleMap.get(loc.imgID)) {
let divimage = document.createElement("div");
divimage.className = "divimageM";
divimage.innerHTML = dImgEle.outerHTML;
dropEle.parentNode.insertBefore(divimage, dropEle);
//添加拖放开始事件,用于删除拖放的标签
let dropImg = divimage.firstChild;
dropImg.id = `${loc.spanID}_${loc.imgID}`;
dropImg.addEventListener("dragstart", info.ePubEidterImgDelStartFun(info, loc));
}
}
}
//匹配插图正则
for (let reg of info.ePubEidtImgRegExp) {
if (reg.test(dropEle.innerText)) {
let liEle = document.createElement("li");
ImgULEle.appendChild(liEle);
let aEle = document.createElement("a");
liEle.appendChild(aEle);
aEle.href = "javascript:void(0);";
aEle.setAttribute("SpanID", dropEle.id);
aEle.innerText = dropEle.innerText.replace(/\s/g, '');
liEle.addEventListener("click", info.ePubEidterImgULFun(info, dropEle));
break;
}
}
}
//加载章节列表
let ChapterULEle = document.getElementById("ChapterUL");
ChapterULEle.innerHTML = "";
let toc = info.nav_toc.find(i => i.volumeID == text.id);
for (let chapter of toc.chapterArr) {
let liEle = document.createElement("li");
ChapterULEle.appendChild(liEle);
let aEle = document.createElement("a");
liEle.appendChild(aEle);
aEle.href = "javascript:void(0);";
aEle.setAttribute("chapterID", chapter.chapterID);
aEle.innerText = chapter.chapterName;
liEle.addEventListener("click", info.ePubEidterChapterULFun(info, chapter));
}
//滚动到分卷开始
VolumeTextEle.scroll({ top: 0 });
VolumeImgEle.scroll({ top: 0 });
};
},//点击分卷事件
ePubEidterImgDropFun: (info, text) => {
return (ev) => {
const data = ev.dataTransfer.getData("text/html");
let divimage = document.createElement("div");
divimage.className = "divimageM";
divimage.innerHTML = data;
let dropImg = divimage.firstChild;
let imgLocation = { "vid": text.vid, "spanID": ev.target.id, "imgID": dropImg.getAttribute("imgID") };
if (info.ePubEidterImgLocation.find(i =>
i.spanID == imgLocation.spanID
&& i.vid == imgLocation.vid
&& i.imgID == imgLocation.imgID)
) {
alert("此位置已存在相同的图片");
}
else {
ev.target.parentNode.insertBefore(divimage, ev.target);
info.ePubEidterImgLocation.push(imgLocation);
//添加拖放开始事件,用于删除拖放的标签
dropImg.id = `${imgLocation.spanID}_${imgLocation.imgID}`;
dropImg.addEventListener("dragstart", info.ePubEidterImgDelStartFun(info, imgLocation));
info.ePubEidterCfg.ImgLocation = info.ePubEidterImgLocation;
let cfgAreaEle = document.getElementById("CfgArea");
cfgAreaEle.value = JSON.stringify(info.ePubEidterCfg, null, " ");
//JSON.parse(cfgAreaEle.value);
}
}
},//插图拖放完成事件
ePubEidterChapterULFun: (info, chapter) => {
return (ev) => {
let VolumeTextEle = document.getElementById("VolumeText");
let target = document.getElementById(chapter.chapterID);
VolumeTextEle.scroll({
top: target.offsetTop,
behavior: 'smooth'
});
//(document.getElementById(chapter.chapterID)).scrollIntoView();
}
},//点击章节事件
ePubEidterImgULFun: (info, dropEle) => {
return (ev) => {
let VolumeTextEle = document.getElementById("VolumeText");
VolumeTextEle.scroll({
top: dropEle.offsetTop - 50,
behavior: 'smooth'
});
}
},//点击推测插图位置事件
ePubEidterSend: (info, url, map) => {
let iframeEle = document.createElement("iframe");
iframeEle.style.display = 'none';
document.body.appendChild(iframeEle);
let iBodyEle = iframeEle.contentWindow.document.body;
let iDocument = iframeEle.contentWindow.document;
let formEle = iDocument.createElement("form");
formEle.acceptCharset = "gbk";
formEle.method = "POST";
formEle.action = url;
iBodyEle.appendChild(formEle);
for (let [mk, mv] of map) {
let inputEle = iDocument.createElement("input");
inputEle.type = "text";
inputEle.name = mk;
inputEle.value = mv;
formEle.appendChild(inputEle);
}
let subEle = iDocument.createElement("input");
subEle.type = "submit";
subEle.name = "submit";
subEle.value = "submit";
formEle.appendChild(subEle);
subEle.click();
},//发送Post请求,无需转gbk
ePubEidterImgDelDropFun: (info) => {
return (ev) => {
let vid = ev.dataTransfer.getData("vid");
let spanID = ev.dataTransfer.getData("spanID");
let imgID = ev.dataTransfer.getData("imgID");
let fromID = ev.dataTransfer.getData("fromID");
let fromEle = document.getElementById(fromID);
if (fromEle && "divimageM" == fromEle.parentElement.className) {
info.ePubEidterImgLocation =
info.ePubEidterImgLocation.filter(i => !(i.spanID == spanID && i.vid == vid && i.imgID == imgID));
info.ePubEidterCfg.ImgLocation = info.ePubEidterImgLocation;
let cfgAreaEle = document.getElementById("CfgArea");
cfgAreaEle.value = JSON.stringify(info.ePubEidterCfg, null, " ");
fromEle.parentElement.parentElement.removeChild(fromEle.parentElement);
}
}
},//插图拖放完成事件
ePubEidterImgDelStartFun: (info, imgLocation) => {
return (ev) => {
ev.dataTransfer.setData("vid", imgLocation.vid);
ev.dataTransfer.setData("spanID", imgLocation.spanID);
ev.dataTransfer.setData("imgID", imgLocation.imgID);
ev.dataTransfer.setData("fromID", ev.srcElement.id);
}
},//插图拖放开始事件
};
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.className = "DownloadAll";
allaEpubEle.setAttribute("DownloadAll", "true");
allaEpubEle.innerText = " ePub下载(全本)";
allaEpubEle.href = "javascript:void(0);";
document.getElementById('title').append(allaEpubEle);
allaEpubEle.addEventListener("click", EpubBuilder);
let allaEpubEleEdt = document.createElement("a");
allaEpubEleEdt.className = "DownloadAll";
allaEpubEleEdt.setAttribute("ePubEidt", "true");
allaEpubEleEdt.setAttribute("DownloadAll", "true");
allaEpubEleEdt.innerText = " (调整插图)";
allaEpubEleEdt.href = "javascript:void(0);";
document.getElementById('title').append(allaEpubEleEdt);
allaEpubEleEdt.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);
let allaEleEdt = document.createElement("a");
allaEleEdt.href = "javascript:void(0);";
allaEleEdt.innerText = " (调整插图)";
allaEleEdt.setAttribute("ePubEidt", "true");
v.append(allaEleEdt);
allaEleEdt.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('下载失败');
}
},
});
}
}
},
});
}
}
}
}
//评论页面
let articleReg = /\/modules\/article\//;
if (articleReg.test(window.location.href)) {
let rid = hrefUrl.searchParams.get('rid');
let page = hrefUrl.searchParams.get('page');
let codeEleArr = document.getElementsByClassName("jieqiCode");
let yidSet = new Set();
for (let code of codeEleArr) {
let yidDivEle = code.parentElement.parentElement;
let yid;
for (let aEle of yidDivEle.getElementsByTagName('a')) {
yid = aEle.getAttribute("name");
if (yid) { break; }
}
if (rid && yid) {
let codeJson = code.innerText.replace(/\s/g,'');
let locCfg
try {
locCfg = JSON.parse(codeJson);
}
catch (e) {
console.log(e);
continue;
}
if (locCfg
&& ePubEidterCfgUID == locCfg.UID
&& locCfg.aid
&& locCfg.pathname
&& (locCfg.ImgLocationBase64 ||(locCfg.ImgLocation && 0 < locCfg.ImgLocation.length))
&& (!yidSet.has(yid))
) {
yidSet.add(yid);
let titleDivEle = yidDivEle.firstElementChild;
let epubRefEle = document.createElement('a');
epubRefEle.innerText = '[使用配置生成ePub]';
epubRefEle.style.color = "fuchsia";
epubRefEle.href = `${locCfg.pathname}?rid=${rid}&page=${page ? page : "1"}&yid=${yid}&CfgRef=1`;
titleDivEle.insertBefore(epubRefEle, titleDivEle.firstElementChild);
}
}
}
}
//读取到的配置
let ePubEidterCfgRef = [];
///modules/article/reviewshow.php?rid=270583
if ("1" == hrefUrl.searchParams.get('CfgRef')) {
const ridCfg = hrefUrl.searchParams.get('rid');
const pageCfg = hrefUrl.searchParams.get('page');
const yidCfg = hrefUrl.searchParams.get('yid');
if (ridCfg && yidCfg) {
let articleUrl = `${hrefUrl.origin}/modules/article/reviewshow.php?rid=${ridCfg}&page=${pageCfg}`;
GM_xmlhttpRequest({
method: 'GET',
url: articleUrl,
onload: function (response) {
if (response.status == 200) {
ePubEidterCfgRef = [];
let domParser = new DOMParser();
let rspHtml = domParser.parseFromString(response.responseText, "text/html");
let codeEleArr = rspHtml.getElementsByClassName("jieqiCode");
for (let code of codeEleArr) {
let yidDivEle = code.parentElement.parentElement;
let yid;
for (let aEle of yidDivEle.getElementsByTagName('a')) {
yid = aEle.getAttribute("name");
if (yid) { break; }
}
if (yid && yidCfg == yid) {
let codeJson = code.innerText.replace(/\s/g, '');
let locCfg
try {
locCfg = JSON.parse(codeJson);
}
catch (e) {
console.log(e);
continue;
}
//解压
if (locCfg.ImgLocationBase64) {
let zip = new JSZip();
let textDec = new TextDecoder();
zip.load(locCfg.ImgLocationBase64, { base64: true });
let fileArry = zip.file(ImgLocationFile)._data.getContent();
let imgLocJson = textDec.decode(fileArry);
let ImgLocation = JSON.parse(imgLocJson);
locCfg.ImgLocation = ImgLocation;
}
if (locCfg
&& ePubEidterCfgUID == locCfg.UID
&& locCfg.aid
&& locCfg.pathname
&& locCfg.ImgLocation
&& 0 < locCfg.ImgLocation.length
) {
ePubEidterCfgRef.push(locCfg);
}
}
}
}
else {
console.log(articleUrl);
console.log("配置下载失败");
}
},
onerror: () => {
console.log(articleUrl);
console.log("配置下载失败");
}
});
}
}
const ePubEidterHtml = `
<div class="main">
<!--左 章节-->
<div id="left">
<div class="block">
<div class="blocktitle">
<span class="txt">操作设置</span>
<span class="txtr"></span>
</div>
<div class="blockcontent">
<div style="padding-left:10px">
<ul class="ulrow">
<li>
<label for="SendArticle">将配置发送到书评:</label>
<input type="checkbox" id="SendArticle" />
</li>
<li>
<label for="ePubEditerClose">生成后自动关闭:</label>
<input type="checkbox" id="ePubEditerClose" checked="true" />
</li>
<li>配置内容:</li>
<li>
<textarea id="CfgArea" class="textarea"></textarea>
</li>
<li><input type="button" id="EidterImportBtn" class="button" value="导入配置" /></li>
<li><input type="button" id="EidterBuildBtn" class="button" value="生成ePub" /></li>
</ul>
<div class="cb"></div>
</div>
</div>
</div>
<div class="block">
<div class="blocktitle">
<span class="txt">推测插图位置</span>
<span class="txtr"></span>
</div>
<div class="blockcontent">
<div style="padding-left:10px">
<ul id="ImgUL" class="ulrow">
</ul>
<div class="cb"></div>
</div>
</div>
</div>
<div class="block">
<div class="blocktitle">
<span class="txt">分卷章节</span>
<span class="txtr"></span>
</div>
<div class="blockcontent">
<div style="padding-left:10px">
<strong>分卷</strong>
<ul id="VolumeUL" class="ulrow">
</ul>
<strong>章节</strong>
<ul id="ChapterUL" class="ulrow">
</ul>
<div class="cb"></div>
</div>
</div>
</div>
</div>
<!--右 内容-->
<div id="centerm">
<!--内容-->
<div id="content">
<table class="grid" width="100%" align="center">
<tbody>
<tr>
<td width="4%" align="center"><span style="font-size:16px;">分<br>卷<br>插<br>图</span></td>
<td>
<div ondragover="return false" id="VolumeImg" style="height:155px;overflow:auto">
</div>
</td>
</tr>
</tbody>
</table>
<table class="grid" width="100%" align="center">
<caption>分卷内容</caption>
<tbody>
<tr>
<td>
<div id="VolumeText" style="height:500px;overflow: hidden scroll ;max-width: 900px;">
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
`;
// Your code here...
})();