生成分卷和全本ePub文档、ePub文档插图拖放、部分小说的在线阅读
// ==UserScript== // @name 轻小说文库下载 // @namespace wenku8Haoa // @version 2.2.2 // @description 生成分卷和全本ePub文档、ePub文档插图拖放、部分小说的在线阅读 // @author HaoaW // @match *://www.wenku8.net/* // @match *://www.wenku8.cc/* // @connect wenku8.com // @connect 777743.xyz // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/full.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/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"; const xmlIllegalCharacters = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g; function OpenCCConver() { let OpenCCInfo = { OpenCCCookieKey: "OpenCCwenku8",//存放设置的cookie的key OpenCCCookie: null,//存放设置的cookie的值 translateButtonId: translateButtonId,//GB_BIG5转换元素ID GB_BIG5_Simplized: Simplized,//GB_BIG5转换方法 GB_BIG5_Traditionalized: Traditionalized,//GB_BIG5转换方法 currentEncoding: currentEncoding,// 1: 繁體中文, 2: 简体中文 targetEncodingCookie: targetEncodingCookie,//GB_BIG5翻译目标 translateBody: translateBody,//翻译元素方法 setCookie: setCookie,//设置cookie getCookie: getCookie,//读cookie CookieDays: 7,//cookie天数 OpenCCEle: null,//开关元素 OpenCCEleClick: () => { //关闭 if (OpenCCInfo.OpenCCCookie) { OpenCCInfo.setCookie(OpenCCInfo.OpenCCCookieKey, "", OpenCCInfo.CookieDays); location.reload(); } //开启 else { OpenCCInfo.setCookie(OpenCCInfo.targetEncodingCookie, "2", OpenCCInfo.CookieDays); OpenCCInfo.setCookie(OpenCCInfo.OpenCCCookieKey, "1", OpenCCInfo.CookieDays); location.reload(); } },//开关元素点击事件 start: () => { OpenCCInfo.OpenCCEle = document.createElement("a"); OpenCCInfo.OpenCCEle.href = "javascript:void(0);" OpenCCInfo.OpenCCEle.innerHTML = "開啟(OpenCC)"; OpenCCInfo.OpenCCEle.addEventListener("click", OpenCCInfo.OpenCCEleClick); //如果有设置就替换GB_BIG5的转换 if (OpenCCInfo.OpenCCCookie) { OpenCCInfo.OpenCCEle.innerHTML = "关闭(OpenCC)"; Traditionalized = OpenCC.Converter({ from: "cn", to: "tw" }); Simplized = OpenCC.Converter({ from: "tw", to: "cn" }); if ("1" == OpenCCInfo.OpenCCCookie) { targetEncoding = OpenCCInfo.OpenCCCookie; translateBody(); } } //添加开关元素 let tranBtn = document.querySelector(`#${OpenCCInfo.translateButtonId}`); if (tranBtn) { tranBtn.parentElement.appendChild(document.createTextNode(" ")); tranBtn.parentElement.appendChild(OpenCCInfo.OpenCCEle); } },// }; OpenCCInfo.OpenCCCookie = OpenCCInfo.getCookie(OpenCCInfo.OpenCCCookieKey); return OpenCCInfo.start; };//使用OpenCCC进行简转繁 OpenCCConver()(); function AppApi() { let AppApiInfo = { VolumeMap: new Map(), appApiDomain: "app.wenku8.com",//app接口域名 appApiPath: "/android.php",//app接口路径 appApiLangDis:true,//禁用app接口请求繁体内容。由页面自行转换。 appApiLang: (info) => { if (AppApiInfo.appApiLangDis) { return "0"; } //0 simplified Chinese;1 traditional Chinese let rst = "0"; if ("1" == info.targetEncoding) { rst = "1"; } else if ("2" == info.targetEncoding) { rst = "0"; } return rst; },//语言选择 appApiGetEncrypted: (body) => { return `appver=1.0&timetoken=${Number(new Date())}&request=${btoa(body)}`; },//编码请求内容 appApiListLoad: (xhr) => { xhr.start = true; xhr.bookInfo.refreshProgress(xhr.bookInfo,`下载app章节目录;`); let lang = AppApiInfo.appApiLang(xhr.bookInfo); let body = `action=book&do=list&aid=${xhr.bookInfo.aid}&t=${lang}`; body = AppApiInfo.appApiGetEncrypted(body); GM_xmlhttpRequest({ method: 'POST', url: xhr.url, headers: { "content-type": "application/x-www-form-urlencoded;charset=utf-8" }, data: body, onload: function (response) { if (response.status == 200) { xhr.done = true; let rspRaw = response.responseText; //格式化xml内容 let domParser = new DOMParser(); let rspXml = domParser.parseFromString(rspRaw.replaceAll(xmlIllegalCharacters, ''), "application/xml"); AppApiInfo.appApiList = rspXml; //继续下载在等待章节列表的分卷 for (let x of AppApiInfo.appApiListWait) { AppApiInfo.appApiLoadVolume(x); } } else { //重新下载 xhr.XHRRetryFun(xhr, `app章节目录下载失败,重新下载;`); } }, onerror: () => { //重新下载 xhr.XHRRetryFun(xhr, `app章节目录下载失败,重新下载;`); } }); },//下载章节列表,全本下载只下载一次 appApiList: null,//章节列表docum,XML appApiListStart: false,//章节列表已开始下载 appApiListWait: [],//等待章节列表的xhr appApiDoList: (xhr) => { if (!AppApiInfo.appApiListStart) { AppApiInfo.appApiListStart = true; //下载章节列表 let dlink = `http://${AppApiInfo.appApiDomain}${AppApiInfo.appApiPath}`; let lXhr = { start: false, done: false, url: dlink, loadFun: AppApiInfo.appApiListLoad, VolumeIndex: xhr.VolumeIndex, bookInfo: xhr.bookInfo }; lXhr.bookInfo.XHRAdd(lXhr); } AppApiInfo.appApiListWait.push(xhr); },//下载章节列表,处理等待队列 appApiLoadVolume: (xhr) => { //如果没有章节列表就去下载 if (!AppApiInfo.appApiList) { AppApiInfo.appApiDoList(xhr); return; } let vol; for (vol of AppApiInfo.appApiList.getElementsByTagName("volume")) { if (xhr.data.vid == vol.getAttribute("vid")) { break; } } //找不到分卷,停止 if (!vol) { xhr.bookInfo.refreshProgress(xhr.bookInfo,`<span style="color:fuchsia;">app章节目录未找到分卷${xhr.data.vid},无法生成ePub;</span>`); xhr.done = false; xhr.bookInfo.XHRFail = true; return; } let chArr = []; //添加章节下载,完成失败的分卷下载;pack.php for (let ch of vol.children) { let cid = ch.getAttribute("cid"); let cName = ch.textContent; //let xhr = { start: false, done: false, url: dlink, loadFun: bInfo.loadVolume, VolumeIndex: VolumeIndex, data: { vid: vid, vcssText: vcssText }, bookInfo: bInfo }; let dlink = `http://${AppApiInfo.appApiDomain}${AppApiInfo.appApiPath}`; let cXhr = { start: false, done: false, url: dlink, loadFun: AppApiInfo.appApiLoadChapter, dealVolume: xhr.dealVolume, VolumeIndex: xhr.VolumeIndex, data: { vid: xhr.data.vid, vcssText: xhr.data.vcssText, Text: xhr.data.Text, isAppApi: true, cid: cid }, bookInfo: xhr.bookInfo }; cXhr.bookInfo.XHRAdd(cXhr); chArr.push({ cid: cid, cName: cName, content :null}); } AppApiInfo.VolumeMap.set(xhr.data.vid, chArr); xhr.done = true; xhr.bookInfo.buildEpub(xhr.bookInfo); },//下载分卷,app接口只能下载章节 appApiLoadChapter: (xhr) => { xhr.start = true; let lang = AppApiInfo.appApiLang(xhr.bookInfo); let body = `action=book&do=text&aid=${xhr.bookInfo.aid}&cid=${xhr.data.cid}&t=${lang}`; body = AppApiInfo.appApiGetEncrypted(body); let msg = `${xhr.data.cName} 下载失败,重新下载;`; GM_xmlhttpRequest({ method: 'POST', url: xhr.url, headers: { "content-type": "application/x-www-form-urlencoded;charset=utf-8" }, data: body, onload: function (response) { if (response.status == 200) { let rspRaw = response.responseText; let chArr = AppApiInfo.VolumeMap.get(xhr.data.vid); let ch = chArr.find(f => f.cid == xhr.data.cid); ch.content = rspRaw; xhr.done = true; let vid = xhr.data.vid; //分卷的章节都下载完成了 if (xhr.bookInfo.XHRDone(vid)) { let VolumeText = ''; //处理格式,拼接章节 for (let c of chArr) { if (!c.content) { continue; } let cName = c.cName; let cid = c.cid; //章节名 c.content = c.content.replace(cName, `<div class="chaptertitle"><a name="${cid}">${cName}</a></div><div class="chaptercontent">`); //换行 c.content = c.content.replace(/\r\n/g, "<br />\r\n"); //替换插图 if (-1 < c.content.indexOf('<!--image-->http')) { c.content = c.content.replaceAll('<!--image-->http', `<div class="divimage" title="http`); c.content = c.content.replaceAll('<!--image-->', `"></div>`); } c.content += `</div>`; VolumeText += c.content; } //处理分卷文本 xhr.dealVolume(xhr, VolumeText); } } else { //重新下载 xhr.XHRRetryFun(xhr, msg); } }, onerror: () => { //重新下载 xhr.XHRRetryFun(xhr, msg); } }); },//下载章节,全部完成后拼成分卷格式 }; return AppApiInfo.appApiLoadVolume; };//用app接口下载分卷、章节 function LoadVolume() { let LoadVolumeInfo = { imgDomain:'img.wenku8.com', appApiLoadVolume: AppApi(),//调用app接口下载文档 loadVolume: (xhr) => { let navToc = xhr.bookInfo.nav_toc[xhr.VolumeIndex]; let msg = `${navToc.volumeName} 下载失败,重新下载;`; xhr.start = true; GM_xmlhttpRequest({ method: 'GET', url: xhr.url, onload: function (response) { if (response.status == 200) { xhr.done = true; LoadVolumeInfo.dealVolume(xhr, response.responseText); } //部分小说会404,用app接口 else if (404 == response.status) { xhr.dealVolume = LoadVolumeInfo.dealVolume; LoadVolumeInfo.appApiLoadVolume(xhr); } else { //重新下载 xhr.XHRRetryFun(xhr, msg); } }, onerror: () => { //重新下载 xhr.XHRRetryFun(xhr, msg); } }); },//分卷下载 ImagesFix: "Img",//图片文件、ID前缀 SpanFix: "Txt",//文字ID前缀 loadImg: (xhr) => { xhr.start = true; let msg = `${xhr.images.idName} 下载失败,重新下载;`; 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.XHRRetryFun(xhr, msg); } }, onerror: () => { //重新下载 xhr.XHRRetryFun(xhr, msg); } }); },//图片下载 dealVolume: (xhr, txt) => { let chapterIndex = 0; let ImagesIndex = 0; let TextIndex = 0; let Text = xhr.data.Text; let navToc = Text.navToc; //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>${xhr.data.vcssText}</title> <link href="../Styles/default.css" rel="stylesheet" type="text/css"/> </head> <body><div class="volumetitle"><h2>${xhr.data.vcssText}</h2></div><br /></body> </html>`.replaceAll(xmlIllegalCharacters, '') , "text/html"); rspHtml.body.innerHTML += txt; //调用简转繁 if (currentEncoding != targetEncoding) { translateBody(rspHtml.body); } //HTML DOM 中的 HTMLCollection 是即时更新的(live);当其所包含的文档结构发生改变时,它会自动更新。 //因此,最好是创建副本(例如,使用 Array.from)后再迭代这个数组以添加、移动或删除 DOM 节点。 let removeChild = []; //处理章节、插图 和 contentdp let bodyChildArr = Array.from(rspHtml.body.children); for (let i = 0; i < bodyChildArr.length; i++) { let child = bodyChildArr[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; if (child.firstChild.hasAttribute("name")) { child.firstChild.remove("name"); } //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; if (navToc) { //添加章节导航 navToc.chapterArr.push({ chapterName: cTitle , chapterID: divEle.id , chapterHref: `${navToc.volumeHref}#${divEle.id}` }); } //章节名接受拖放 let txtSpan = rspHtml.createElement("span"); txtSpan.id = `${LoadVolumeInfo.SpanFix}_${divEle.id}`; txtSpan.className = "txtDropEnable"; txtSpan.setAttribute("ondragover", "return false"); child.parentElement.insertBefore(txtSpan, child); txtSpan.appendChild(child); } //内容 else if ("DIV" == child.tagName && "chaptercontent" == child.className) { let chapterChildArr = Array.from(child.childNodes); for (let j = 0; j < chapterChildArr.length; j++) { let contentChild = chapterChildArr[j]; //文字 if (Node.TEXT_NODE == contentChild.nodeType && contentChild.textContent != '\n') { TextIndex++; let txtSpan = rspHtml.createElement("span"); txtSpan.id = `${LoadVolumeInfo.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}`; let imgURL = new URL(imgASrc); let pathNameArr = imgURL.pathname.split('/'); let imgIdName = pathNameArr[pathNameArr.length - 1]; //在html中加入img标签 let imgEle = document.createElement("img"); imgEle.setAttribute("loading", "lazy"); imgEle.setAttribute("src", `../${imgPath}`); contentChild.innerHTML = imgEle.outerHTML; //记录图片信息作为epub资源 ImagesIndex++; let ImagesID = `${LoadVolumeInfo.ImagesFix}_${xhr.VolumeIndex}_${ImagesIndex}`; let images = { path: `${imgPath}`, content: null, id: ImagesID, idName: imgIdName, 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: LoadVolumeInfo.loadImg, images: images, bookInfo: xhr.bookInfo }; xhr.bookInfo.XHRAdd(xhrImg); } } } } removeChild.forEach(c => rspHtml.body.removeChild(c)); Text.content = rspHtml.body.innerHTML; //没有图片则添加书籍缩略图 if (xhr.bookInfo.Images.length==0) { let pathArry = location.pathname.replace('novel','image').split('/'); pathArry.pop(); let ImagesID = `${pathArry.findLast(e => e)}s`; pathArry.push(`${ImagesID}.jpg`); let imgASrc = `https://${LoadVolumeInfo.imgDomain}${pathArry.join('/')}`; let imgUrl = new URL(imgASrc); let imgPath = `Images${imgUrl.pathname}`; let images = { path: `${imgPath}`, content: null, id: ImagesID, idName: ImagesID, TextId: "", smallCover:true }; xhr.bookInfo.Images.push(images); //添加图片下载xhr请求 let xhrImg = { start: false, done: false, url: imgASrc, loadFun: LoadVolumeInfo.loadImg, images: images, bookInfo: xhr.bookInfo }; xhr.bookInfo.XHRAdd(xhrImg); } //生成epub,还有资源未下载则只更新生成进度 xhr.bookInfo.buildEpub(xhr.bookInfo); }, }; return LoadVolumeInfo.loadVolume; };//下载分卷内容及插图 function EPubEidter() { let EPubEidterInfo = { Domain: "www.wenku8.net", novelTable: null, ePubEidterCfg: { UID: ePubEidterCfgUID, aid: article_id, pathname: hrefUrl.pathname, ImgLocation: [] },//插图位置配置 ePubEidtImgRegExp: [/img/i, /插图/i, /插圖/i, /\.jpg/i, /\.png/i],//推测插图位置的正则 ePubEidtLink: [], ePubEidt: false, ePubEidtDone: false, ePubEidterHtml: ePubEidterHtml,//编辑器html代码 ePubEidter: null, ePubEidterLastVolumeUL: null, ePubEidterInit: (info) => { //隐藏目录 let downloadEleArr = document.querySelectorAll(".DownloadAll"); for (let f of downloadEleArr) { f.style.pointerEvents = "none"; } EPubEidterInfo.novelTable = document.body.getElementsByTagName("table")[0]; EPubEidterInfo.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); EPubEidterInfo.ePubEidtLink.push(linkEle); let divEle = document.createElement("div"); divEle.id = "ePubEidter"; divEle.style.display = "none"; linkEle.onload = () => { //显示编辑器 divEle.style.display = ""; }; divEle.innerHTML = EPubEidterInfo.ePubEidterHtml; EPubEidterInfo.ePubEidter = divEle; EPubEidterInfo.novelTable.parentElement.insertBefore(divEle, info.novelTable); document.getElementById("EidterBuildBtn").addEventListener("click", EPubEidterInfo.ePubEidterDoneFun(info)); document.getElementById("EidterImportBtn").addEventListener("click", EPubEidterInfo.ePubEidterImportCfgFun(info)); document.getElementById("VolumeImg").addEventListener("drop", EPubEidterInfo.ePubEidterImgDelDropFun(info)); //加载配置内容 let cfgAreaEle = document.getElementById("CfgArea"); EPubEidterInfo.ePubEidterCfg.ImgLocation = info.ImgLocation; cfgAreaEle.value = JSON.stringify(EPubEidterInfo.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", EPubEidterInfo.ePubEidterVolumeULFun(info, text)); if (!liEleFirst) { liEleFirst = liEle; } } //加载第一卷 if (liEleFirst) { liEleFirst.click(); } },//编辑器初始化 ePubEidterDestroyer: () => { EPubEidterInfo.ePubEidter.parentElement.removeChild(EPubEidterInfo.ePubEidter); EPubEidterInfo.ePubEidtLink.forEach(f => f.parentElement.removeChild(f)); EPubEidterInfo.novelTable = document.body.getElementsByTagName("table")[0]; EPubEidterInfo.novelTable.style.display = ""; EPubEidterInfo = null; let downloadEleArr = document.querySelectorAll(".DownloadAll"); for (let f of downloadEleArr) { f.style.pointerEvents = "auto"; } },//编辑器销毁 ePubEidterDoneFun: (info) => { return (ev) => { ev.currentTarget.disabled = true; //生成ePub info.ePubEidtDone = true; info.buildEpub(info); //发送配置 let sendArticleEle = document.getElementById('SendArticle'); if (sendArticleEle.checked && 0 < info.ImgLocation.length) { let cfgObj = Object.assign({}, EPubEidterInfo.ePubEidterCfg); //压缩位置 let imgLocJson = JSON.stringify(info.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.ImgLocation) { 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://${EPubEidterInfo.Domain}/modules/article/reviews.php?aid=${info.aid}`; //发送配置 EPubEidterInfo.ePubEidterSend(info, url, map); } let ePubEditerClose = document.getElementById('ePubEditerClose'); ev.currentTarget.disabled = false; if (ePubEditerClose.checked) { EPubEidterInfo.ePubEidterDestroyer(); } }; },//点击生成ePub事件 ePubEidterImportCfgFun: (info) => { return (ev) => { ev.currentTarget.disabled = true; let cfgAreaEle = document.getElementById("CfgArea"); let impCfg; try { impCfg = JSON.parse(cfgAreaEle.value); } catch { } if (impCfg && impCfg.UID == EPubEidterInfo.ePubEidterCfg.UID && impCfg.aid == EPubEidterInfo.ePubEidterCfg.aid && impCfg.ImgLocation && 0 < impCfg.ImgLocation.length ) { for (let iCfg of impCfg.ImgLocation) { if (info.ImgLocation.find(i => i.spanID == iCfg.spanID && i.vid == iCfg.vid && i.imgID == iCfg.imgID) ) { continue; } else if (!info.Text.find(f => f.vid == iCfg.vid)) { continue; } else { info.ImgLocation.push(iCfg); } } } EPubEidterInfo.ePubEidterCfg.ImgLocation = info.ImgLocation; cfgAreaEle.value = JSON.stringify(EPubEidterInfo.ePubEidterCfg, null, " "); if (EPubEidterInfo.ePubEidterLastVolumeUL) { EPubEidterInfo.ePubEidterLastVolumeUL.click(); } ev.currentTarget.disabled = false; }; },//点击导入配置事件 ePubEidterVolumeULFun: (info, text) => { return (ev) => { //最后点击的章节列表,导入配置后刷新 if (EPubEidterInfo.ePubEidterLastVolumeUL) { EPubEidterInfo.ePubEidterLastVolumeUL.firstElementChild.style.color = ""; } EPubEidterInfo.ePubEidterLastVolumeUL = ev.currentTarget; EPubEidterInfo.ePubEidterLastVolumeUL.firstElementChild.style.color = "fuchsia"; //加载文本内容 let VolumeTextEle = document.getElementById("VolumeText"); VolumeTextEle.style.display = "none"; 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.ImgLocation.filter(i => text.vid == i.vid); //拖放处理绑定 let dropEleArr = document.querySelectorAll(".txtDropEnable"); for (let dropEle of dropEleArr) { dropEle.addEventListener("drop", EPubEidterInfo.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", EPubEidterInfo.ePubEidterImgDelStartFun(info, loc)); } } } //章节名不测试 if (!dropEle.firstElementChild || "chaptertitle" != dropEle.firstElementChild.className) { //匹配插图正则 for (let reg of EPubEidterInfo.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, '').substring(0, 12); liEle.addEventListener("click", EPubEidterInfo.ePubEidterImgULFun(info, dropEle)); dropEle.style.color = "fuchsia";//fontWeight = "bold"; 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", EPubEidterInfo.ePubEidterChapterULFun(info, chapter)); } VolumeTextEle.style.display = ""; //滚动到分卷开始 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.currentTarget.id, "imgID": dropImg.getAttribute("imgID") }; if (info.ImgLocation.find(i => i.spanID == imgLocation.spanID && i.vid == imgLocation.vid && i.imgID == imgLocation.imgID) ) { alert("此位置已存在相同的图片"); } else { ev.currentTarget.parentNode.insertBefore(divimage, ev.currentTarget); info.ImgLocation.push(imgLocation); //添加拖放开始事件,用于删除拖放的标签 dropImg.id = `${imgLocation.spanID}_${imgLocation.imgID}`; dropImg.addEventListener("dragstart", EPubEidterInfo.ePubEidterImgDelStartFun(info, imgLocation)); EPubEidterInfo.ePubEidterCfg.ImgLocation = info.ImgLocation; let cfgAreaEle = document.getElementById("CfgArea"); cfgAreaEle.value = JSON.stringify(EPubEidterInfo.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 - 130, 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.ImgLocation = info.ImgLocation.filter(i => !(i.spanID == spanID && i.vid == vid && i.imgID == imgID)); EPubEidterInfo.ePubEidterCfg.ImgLocation = info.ImgLocation; let cfgAreaEle = document.getElementById("CfgArea"); cfgAreaEle.value = JSON.stringify(EPubEidterInfo.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); } },//插图拖放开始事件 }; return [EPubEidterInfo.ePubEidterInit, EPubEidterInfo.ePubEidterCfg]; };//epub编辑器,拖动调整插图位置 function XHRDownloader() { let XHRDownloaderInfo = { XHRFail: false,//下载失败,不生成ePub XHRRetry: 3,//xhr重试次数 XHRRetryFun: (xhr, msg) => { //下载失败,不重试,不会生成ePub if (XHRDownloaderInfo.XHRFail) { return; } if ( (!xhr.XHRRetryCount) || 0 == XHRDownloaderInfo.XHRRetry || xhr.XHRRetryCount < XHRDownloaderInfo.XHRRetry ) { xhr.XHRRetryCount = (xhr.XHRRetryCount ?? 0) + 1; xhr.loadFun(xhr); xhr.bookInfo.refreshProgress(xhr.bookInfo,msg); } else { XHRDownloaderInfo.XHRFail = true; xhr.bookInfo.refreshProgress(xhr.bookInfo, `<span style="color:fuchsia;">超出最大重试次数,下载失败,无法生成ePub;</span>`); } },//xhr重试 XHRArr: [],//下载请求;[{start:false,done:false,url:,loadFun:,data:,bookInfo:bInfo}] XHRAdd: (xhr) => { xhr.XHRRetryFun = xhr.XHRRetryFun ?? XHRDownloaderInfo.XHRRetryFun; XHRDownloaderInfo.XHRArr.push(xhr); xhr.loadFun(xhr); }, XHRDone: (vid) => { let arr = XHRDownloaderInfo.XHRArr; if (vid) { arr = arr.filter(f => f.data && f.data.vid && vid == f.data.vid); } return arr.every(e => e.done); }, }; return [XHRDownloaderInfo.XHRAdd, XHRDownloaderInfo.XHRDone ]; };//XHR下载、重试 function Builder() { let BuilderInfo = { mimetype: 'application/epub+zip',//epub mimetype 文件内容 container_xml: container_xml,//epub container.xml 文件内容 nav_xhtml: { content: nav_xhtml_content , path: `Text/nav.xhtml` , id: `nav_xhtml_id` },//epub nav.xhtml 文件模板 defaultCSS: { content: defaultCSS_content , id: "default_css_id" , path: "Styles/default.css" },//epub default.css 样式文件 contentDocument: null, manifest: null, spine: null, manifestItemAdd: (id, href, mediaType) => { let doc = BuilderInfo.contentDocument let manifest = BuilderInfo.manifest; if (!manifest) { manifest = doc.createElement("manifest"); doc.firstChild.appendChild(manifest); BuilderInfo.manifest = manifest; } let item = doc.createElement("item"); if ('undefined' != typeof (id)) { item.setAttribute("id", id); } if ('undefined' != typeof (href)) { item.setAttribute("href", href); } if ('undefined' != typeof (mediaType)) { item.setAttribute("media-type", mediaType); } manifest.appendChild(item); return item; }, spineItemAdd: (idref) => { let doc = BuilderInfo.contentDocument let spine = BuilderInfo.spine; if (!spine) { spine = doc.createElement("spine"); doc.firstChild.appendChild(spine); BuilderInfo.spine = spine; } let itemref = doc.createElement("itemref"); if ('undefined' != typeof (idref)) { itemref.setAttribute("idref", idref); } spine.appendChild(itemref); return itemref; }, manifestSpineItemAdd: (id, href, mediaType) => { let item = BuilderInfo.manifestItemAdd(id, href, mediaType); let itemref = BuilderInfo.spineItemAdd(id); return [item, itemref]; }, buildEpub: (info) => { if (info.XHRDone()) { 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", BuilderInfo.mimetype); zip.file("META-INF/container.xml", BuilderInfo.container_xml); let content_opf = `<?xml version="1.0" encoding="utf-8"?><package></package>`; let paraser = new DOMParser() BuilderInfo.contentDocument = paraser.parseFromString(content_opf,'text/xml') //保存插图位置 if (info.ePubEidterCfg && info.ePubEidterCfg.ImgLocation && 0 < info.ePubEidterCfg.ImgLocation.length) { let cfgJson = JSON.stringify(info.ePubEidterCfg, null, " "); zip.file("OEBPS/Other/ePubEidterCfg.json", cfgJson); } //保存css { BuilderInfo.manifestItemAdd(BuilderInfo.defaultCSS.id, BuilderInfo.defaultCSS.path, "text/css"); //保存css zip.file(`OEBPS/${BuilderInfo.defaultCSS.path}`, BuilderInfo.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(BuilderInfo.nav_xhtml.content.replaceAll(xmlIllegalCharacters, ''), "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, itemref] = BuilderInfo.manifestSpineItemAdd( BuilderInfo.nav_xhtml.id, BuilderInfo.nav_xhtml.path, "application/xhtml+xml" ); item.setAttribute("properties", "nav"); itemref.setAttribute("linear", "no"); //保存nav.xhtml zip.file(`OEBPS/${BuilderInfo.nav_xhtml.path}`, nav_xhtml); } //保存分卷内容 for (let t of info.Text) { BuilderInfo.manifestSpineItemAdd(t.id, t.path, "application/xhtml+xml"); //转换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>`.replaceAll(xmlIllegalCharacters, '') , "text/html"); rspHtml.body.innerHTML = t.content; //添加插图并去除拖放标签 let vLocation = info.ImgLocation.filter(i => t.vid == i.vid); let volumeImgs = info.Images.filter(i => i.TextId == t.id); let dropEleArr = rspHtml.querySelectorAll(".txtDropEnable"); for (let dropEle of dropEleArr) { //加载已拖放的图片 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.replaceAll(xmlIllegalCharacters, ''), "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) { //media-type暂固定jpeg BuilderInfo.manifestItemAdd(t.id, t.path, "image/jpeg"); 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) { coverImg = info.Images.find(i => i.smallCover); } if (coverImg) { coverMeta = `<meta name="cover" content="${coverImg.id}" />`; } } let uuid = self.crypto.randomUUID(); //CCYY-MM-DDThh:mm:ssZ let createTime = new Date().toISOString(); createTime = `${createTime.split(".")[0]}Z`; //<?xml version="1.0" encoding="utf-8"?> 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>${info.title}</dc:title> <meta property="dcterms:modified">${createTime}</meta> <dc:identifier id="BookId">urn:uuid:${uuid}</dc:identifier> <dc:creator>${info.creator}</dc:creator> <!--第一张插图做封面--> ${coverMeta} </metadata> ${BuilderInfo.manifest.outerHTML} ${BuilderInfo.spine.outerHTML} </package>`; zip.file("OEBPS/content.opf", content_opf); //书名.开始卷-结束卷.epub let epubName = `${info.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已经都完成了 }; return BuilderInfo.buildEpub; };//epub生成 function RefreshLog() { let LogInfo = { progressEle: null,//进度、日志 元素;{txt:,img:,err:} refreshProgress: (info,err) => { if (!LogInfo.progressEle) { //epub生成进度,下载文本进度:7/7;下载图片进度:77/77;日志:开始生成ePub;ePub生成完成; LogInfo.progressEle = {}; LogInfo.progressEle.txt = document.createElement("span"); LogInfo.progressEle.img = document.createElement("span"); LogInfo.progressEle.err = document.createElement("span"); let logDiv = document.createElement('div'); logDiv.appendChild(document.createTextNode("epub生成进度,下载文本进度:")); logDiv.appendChild(LogInfo.progressEle.txt); logDiv.appendChild(document.createTextNode(";下载图片进度:")); logDiv.appendChild(LogInfo.progressEle.img); logDiv.appendChild(document.createTextNode(";日志:")); logDiv.appendChild(LogInfo.progressEle.err); document.body.insertBefore(logDiv, document.getElementById('title')); } //日志 if (err) { LogInfo.progressEle.err.innerHTML = err + LogInfo.progressEle.err.innerHTML; } //文本进度 let txtProgress = info.Text.filter((value) => { return value.content; }).length; LogInfo.progressEle.txt.innerText = `${txtProgress}/${info.Text.length}`; //图片进度,文本下载完成后才能得到图片总数 if (txtProgress == info.Text.length) { let imgProgress = info.Images.filter((value) => { return value.content; }).length; LogInfo.progressEle.img.innerText = `${imgProgress}/${info.Images.length}`; } },//显示进度日志 }; return LogInfo.refreshProgress; }; function EpubBuilder() { let bInfo = { XHRAdd: null, XHRDone: null, ePubEidterInit: null, ePubEidterCfg: null, buildEpub: Builder(), loadVolume: LoadVolume(),//下载章节方法 refreshProgress: RefreshLog(), start: (e) => { [bInfo.XHRAdd, bInfo.XHRDone] = XHRDownloader(); let ePubEidt = e.target.getAttribute("ePubEidt"); if (ePubEidt && "true" == ePubEidt) { bInfo.ePubEidt = true; [bInfo.ePubEidterInit, bInfo.ePubEidterCfg] = EPubEidter(); } //全本分卷 let vcssEle = null; let DownloadAll = e.target.getAttribute("DownloadAll"); if (DownloadAll && "true" == DownloadAll) { //全本下载 vcssEle = document.querySelectorAll(".vcss"); } else { vcssEle = [e.target.parentElement]; } for (let VolumeIndex = 0; VolumeIndex < vcssEle.length; VolumeIndex++) { let vcss = vcssEle[VolumeIndex]; //分卷ID let vid = vcss.getAttribute("vid"); //pack.php下载整卷,每个章节不带卷名,用分卷的第一个章节做分卷vid let vid1 = 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: [] }; let Text = { path: `Text/${navToc.volumeHref}` , content: "" , id: navToc.volumeID , vid: vid , volumeName: vcssText }; Text.navToc = navToc; bInfo.Text[VolumeIndex] = Text; //分卷下载链接 let dlink = `https://${bInfo.dlDomain}/pack.php?aid=${bInfo.aid}&vid=${vid1}`; let xhr = { start: false, done: false, url: dlink, loadFun: bInfo.loadVolume, VolumeIndex: VolumeIndex, data: { vid: vid, vcssText: vcssText, Text: Text }, bookInfo: bInfo }; bInfo.XHRAdd(xhr); } //加载从评论读取的配置 if (bInfo.ImgLocationCfgRef && 0 < bInfo.ImgLocationCfgRef.length) { for (let cfgRef of bInfo.ImgLocationCfgRef) { 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 && bInfo.Text.find(f => f.vid == loc.vid) ) { if (!bInfo.ImgLocation.find(f => f.vid == loc.vid && f.spanID == loc.spanID && f.imgID == loc.imgID )) { bInfo.ImgLocation.push(loc); } } } } } } if (bInfo.ePubEidterCfg && bInfo.ImgLocation && 0 < bInfo.ImgLocation.length) { bInfo.ePubEidterCfg.ImgLocation = bInfo.ImgLocation; } bInfo.buildEpub(bInfo); },//入口,开始下载文件并生成epub; nav_toc: [],//导航菜单,第一层分卷,第二层章节{volumeName:,volumeID:,volumeHref:,chapterArr:[{chapterName:,chapterID:,chapterHref:}]} Text: [],//下载后生成的XHTML;{path:`Text/${volumeHref}`,content:} Images: [],//下载的图片;{path:`Images/${url.pathname}`,content:} VolumeFix: "Volume",//分卷文件、ID前缀 dlDomain: "dl.wenku8.com", ImgLocationCfgRef: ImgLocationCfgRef,//读取到的配置 ImgLocation: [],//插图位置记录{vid:,spanID:,imgID:} targetEncoding: targetEncoding,// 1: 繁體中文, 2: 简体中文 aid: article_id,//本书编号 article_id title: document.getElementById("title").childNodes[0].textContent, //标题 creator: document.getElementById('info').innerText,//作者 bookUrl: self.location.href, }; return bInfo; };//epub生成;使用addEventListener绑定start; //目录或内容页面会声明章节变量。 if ('undefined' == typeof chapter_id || undefined === chapter_id) { } else { //本书编号 article_id //目录页面章节id定义为 '0' if ('0' == chapter_id) {//在章节名之后添加下载链接 //书名 let titleEle = document.querySelector("#title"); let aname = titleEle.innerText; //targetEncoding 1: 繁體中文, 2: 简体中文 let charsetDL = 'utf-8'; let charsetDLAll = 'utf8'; if ('1' == targetEncoding) { charsetDL = 'big5'; charsetDLAll = 'big5'; } //添加全本下载链接 { let DLink = `https://dl.wenku8.com/down.php?type=${charsetDLAll}&id=${article_id}&fname=${aname}`; let aEle = document.createElement("a"); aEle.href = DLink; aEle.innerText = ` 全本文本下载(${charsetDLAll})`; titleEle.appendChild(aEle); //添加 ePub下载(全本) let aEleEpub = document.createElement("a"); aEleEpub.className = "DownloadAll"; aEleEpub.setAttribute("DownloadAll", "true"); aEleEpub.innerText = " ePub下载(全本)"; aEleEpub.href = "javascript:void(0);"; titleEle.append(aEleEpub); aEleEpub.addEventListener("click", (e) => EpubBuilder().start(e)); let allaEpubEleEdt = document.createElement("a"); allaEpubEleEdt.className = "DownloadAll"; allaEpubEleEdt.setAttribute("ePubEidt", "true"); allaEpubEleEdt.setAttribute("DownloadAll", "true"); allaEpubEleEdt.innerText = " (调整插图)"; allaEpubEleEdt.href = "javascript:void(0);"; titleEle.append(allaEpubEleEdt); allaEpubEleEdt.addEventListener("click", (e) => EpubBuilder().start(e)); } //添加分卷下载链接 let vcssArry = document.querySelectorAll(".vcss"); for (let vcss of vcssArry) { let vname = vcss.innerText; let vid = vcss.getAttribute("vid"); let dlink = `https://dl.wenku8.com/packtxt.php?aid=${article_id}&vid=${vid}&aname=${aname}&vname=${vname}&charset=${charsetDL}`; let aEle = document.createElement("a"); aEle.href = dlink; aEle.innerText = ` 文本下载(${charsetDL})`; vcss.appendChild(aEle); //添加 ePub下载(分卷) let aEleEpub = document.createElement("a"); aEleEpub.href = "javascript:void(0);"; aEleEpub.innerText = " ePub下载(本卷)"; vcss.append(aEleEpub); aEleEpub.addEventListener("click", (e) => EpubBuilder().start(e)); let aEleEpubEdt = document.createElement("a"); aEleEpubEdt.href = "javascript:void(0);"; aEleEpubEdt.innerText = " (调整插图)"; aEleEpubEdt.setAttribute("ePubEidt", "true"); vcss.append(aEleEpubEdt); aEleEpubEdt.addEventListener("click", (e) => EpubBuilder().start(e)); } } else { //如果第一个子元素为 内容是'null'的span则判定为版权限制 let contentMain = document.querySelector('#contentmain'); if ("SPAN" == contentMain.firstElementChild.tagName && contentMain.firstElementChild.innerText.trim() == 'null') { let content = document.getElementById("content"); let appApi = { appApiDomain: "app.wenku8.com",//app接口域名 appApiPath: "/android.php",//app接口路径 targetEncoding: targetEncoding, appApiLangDis: true,//禁用app接口请求繁体内容。由页面自行转换。 appApiLang: () => { if (appApi.appApiLangDis) { return "0"; } //0 simplified Chinese;1 traditional Chinese let rst = "0"; if ("1" == appApi.targetEncoding) { rst = "1"; } else if ("2" == appApi.targetEncoding) { rst = "0"; } return rst; },//语言选择 appApiGetEncrypted: (body) => { return `appver=1.0&timetoken=${Number(new Date())}&request=${btoa(body)}`; },//编码请求内容 appApiLoadChapter: (xhr) => { xhr.start = true; let lang = appApi.appApiLang(xhr.bookInfo); let body = `action=book&do=text&aid=${xhr.bookInfo.aid}&cid=${xhr.data.cid}&t=${lang}`; body = appApi.appApiGetEncrypted(body); let msg = `${xhr.data.cName} 下载失败,重新下载;`; GM_xmlhttpRequest({ method: 'POST', url: xhr.url, headers: { "content-type": "application/x-www-form-urlencoded;charset=utf-8" }, data: body, onload: function (response) { if (response.status == 200) { let rspRaw = response.responseText; rspRaw = rspRaw.replace(/ {2}\S+.*/, ""); //换行 rspRaw = rspRaw.replace(/\r\n/g, "<br />\r\n"); //替换插图 if (-1 < rspRaw.indexOf('<!--image-->http')) { rspRaw = rspRaw.replaceAll(/<!--image-->(http[\w:/\.?@#&=%]+)<!--image-->/g, (m, p1) => `<div class="divimage"><a href="${p1}" target="_blank"><img src="${p1}" border="0" class="imagecontent"></a></div>`); } rspRaw += `</div>`; content.innerHTML = rspRaw; appApi.translateBody(content); xhr.done = true; } else { //重新下载 xhr.XHRRetryFun(xhr, msg); } }, onerror: () => { //重新下载 xhr.XHRRetryFun(xhr, msg); } }); },//下载章节,全部完成后拼成分卷格式 XHRAdd: null, refreshProgress: (info, err) => { if (err) { content.innerHTML = err + content.innerHTML; } }, translateBody: translateBody, }; let bookInfo = { aid: article_id, refreshProgress: appApi.refreshProgress }; [bookInfo.XHRAdd] = XHRDownloader(); let dlink = `http://${appApi.appApiDomain}${appApi.appApiPath}`; let xhr = { start: false, done: false, url: dlink, loadFun: appApi.appApiLoadChapter, data: { cid: chapter_id }, bookInfo: bookInfo }; xhr.bookInfo.XHRAdd(xhr); content.innerHTML = '正在下载,请稍候...'; } } } //评论页面 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.querySelectorAll(".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 ImgLocationCfgRef = []; ///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) { let domParser = new DOMParser(); let rspHtml = domParser.parseFromString(response.responseText.replaceAll(xmlIllegalCharacters, ''), "text/html"); let codeEleArr = rspHtml.querySelectorAll(".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 ) { ImgLocationCfgRef.push(locCfg); } } } } else { console.log(articleUrl); console.log("配置下载失败"); } }, onerror: () => { console.log(articleUrl); console.log("配置下载失败"); } }); } } const defaultCSS_content = ` nav#landmarks { display:none; } nav#page-list { display:none; } ol { list-style-type: none; } .volumetitle , .chaptertitle { text-align: center; } `; const 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><a href= "#toc">目录</a></h2> </nav> </body> </html>`; const 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>`; const ePubEidterHtml = ` <div class="main" style="width: 1200px;"> <!--左 章节--> <div id="left"> <div class="block" style="min-height: 230px;"> <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" style="min-height: 230px;"> <div class="blocktitle"> <span class="txt">分卷</span> <span class="txtr"></span> </div> <div class="blockcontent"> <div style="padding-left:10px"> <ul id="VolumeUL" class="ulrow"> </ul> <div class="cb"></div> </div> </div> </div> </div> <!--左 章节--> <div id="left"> <div class="block" style="min-height: 230px;"> <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" style="min-height: 230px;"> <div class="blocktitle"> <span class="txt">章节</span> <span class="txtr"></span> </div> <div class="blockcontent"> <div style="padding-left:10px"> <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... })();