Last active
August 15, 2019 01:33
-
-
Save KCCat/b288d5bae7339c709e4f4d5ab616d0d9 to your computer and use it in GitHub Desktop.
转换轻国论坛帖子到epub. 用法: 0.切换到只看楼主模式 1.鼠标选取章节 2. 猴子扩展里的按钮开始运行 3. 完成后帖子标题转为下载链接
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name lightnovel2epub | |
// @description 快速输出半成品epub3.0 | |
// @author KCC | |
// @version 0.99.25 | |
// @namespace lightnovel2epub | |
// @match *://www.lightnovel.us/forum.php* | |
// @match *://www.lightnovel.cn/forum.php* | |
// @require https://github.com/Stuk/jszip/raw/master/dist/jszip.min.js | |
// @grant GM_registerMenuCommand | |
// @grant GM_setClipboard | |
// @grant GM_xmlhttpRequest | |
// ==/UserScript== | |
var Ngen = (function* () { var i = 0; while(true) {yield '_' + (i++).toString() + '.jpeg';}})(); | |
var Pstarter = () => { | |
var rD,rP; | |
rP = new Promise((D,F) => {rD = D;}); | |
return [rP,rD]; | |
}; | |
var GM_ajax = (b={}) => new Promise((D,F) => { | |
b.onload = (e) => D(e), | |
b.onerror = (e) => F(e); | |
GM_xmlhttpRequest(b); | |
}); | |
var ajax = (b={}) => new Promise((D,F) => { | |
let xhr = new XMLHttpRequest(); | |
xhr.onload = (e) => D(e), | |
xhr.onerror = (e) => F(e); | |
xhr.open(b.method, b.url); | |
xhr.responseType = b.responseType; | |
xhr.send(); | |
}); | |
var f = e => { | |
var d = document.createElement('div'); | |
if(e.nodeName == '#text') { | |
let ne = document.createElement('p'); | |
ne.textContent = e.textContent.trim(); | |
d.append(ne); | |
if(ne.textContent.indexOf('作者')!=-1 | |
&& ne.textContent.split(/\s/).join('').split(/[::]/).length==2 | |
&& opf.split('{{creator}}').length > 1) { | |
opf=opf.split('{{creator}}').join(ne.textContent.split(/\s/).join('').split(/[::]/)[1]); | |
console.log('lightnovel2epub: create epub <p> <img>'); | |
} | |
} else | |
if(e.nodeName == 'IGNORE_JS_OP') { | |
var ne = document.createElement('img'); | |
let sfile = e.querySelector('strong')||e.querySelector('a[id]') | |
if (sfile) { | |
ne.setAttribute('srcepub', 'images/' + sfile.textContent.split(/\s/).join('') + Ngen.next().value); | |
ne.setAttribute('srclink', e.querySelector('a').getAttribute('href')); | |
} else {ne = document.createElement('p');} | |
d.append(ne); | |
} else | |
if(e.nodeName == 'IMG' && e.hasAttribute('file')) { | |
let ne = document.createElement('img'); | |
if (IMGfix) { | |
let url = 'http' + e.getAttribute('file').split('https')[1]; | |
e.src = url; | |
e.setAttribute('file', url); | |
} | |
ne.setAttribute('srcepub', 'images/' + e.getAttribute('file').split('?')[0].split('/').pop() + Ngen.next().value) | |
ne.setAttribute('srclink', e.getAttribute('file')); | |
d.append(ne); | |
} else if(e.className != 'locked') | |
{ | |
d.append(...[...e.childNodes].map(f).reduce((a, e) => a.concat([...e.childNodes]),[])); | |
} | |
return d; | |
}; | |
var img_1 = e => { | |
let sblob = URL.createObjectURL(e.response), | |
timg = document.createElement('img'); | |
Pr = new Promise((D, F) => { | |
timg.addEventListener('load',() => D(timg)) | |
,timg.addEventListener('error',() => F(timg)); | |
}); | |
timg.src = sblob; | |
//URL.revokeObjectURL(sblob); | |
return Pr; | |
}; | |
var img_2 = (timg) => { | |
var Pr; | |
if (timg.naturalWidth > timg.naturalHeight) { | |
let tscanvas = document.createElement('canvas'), | |
tdcanvas = document.createElement('canvas'); | |
tscanvas.width = timg.naturalWidth, | |
tscanvas.height = timg.naturalWidth, | |
tdcanvas.width = timg.naturalHeight, | |
tdcanvas.height = timg.naturalWidth; | |
tsC = tscanvas.getContext('2d'), | |
tdC = tdcanvas.getContext('2d'), | |
thh = ~~(timg.naturalHeight/2); | |
tsC.translate(thh, thh), | |
tsC.rotate(90 * Math.PI / 180), | |
tsC.drawImage(timg, -thh, -thh), | |
tdC.drawImage(tscanvas, 0, 0); | |
Pr = new Promise((D, F) => tdcanvas.toBlob(D, 'image/jpeg', 1)); | |
} else { | |
let tscanvas = document.createElement('canvas'); | |
tscanvas.width = timg.naturalWidth, | |
tscanvas.height = timg.naturalHeight; | |
tsC = tscanvas.getContext('2d'), | |
tsC.drawImage(timg, 0, 0), | |
Pr = new Promise((D, F) => tscanvas.toBlob(D, 'image/jpeg', 1)); | |
} | |
return Pr; | |
}; | |
console.log('lightnovel2epub: IMG url testing ...'); | |
var IMGfix = 0; | |
var querySelectorIMG = document.querySelector('[id*=postmessage_] img[id],.pattl img[id]'); | |
if (querySelectorIMG) { | |
GM_ajax({ | |
method: "HEAD", | |
url: querySelectorIMG.getAttribute('file'), | |
}) | |
.then(() => console.log('lightnovel2epub: IMG testing OK')) | |
.catch((r) => { | |
[...document.querySelectorAll('[id*=postmessage_] img[id],.pattl img[id]')] | |
.forEach(e => { | |
let url = 'http' + e.getAttribute('file').split('https')[1]; | |
e.src = url; | |
e.setAttribute('file', url); | |
}); | |
IMGfix = 1; | |
console.log('lightnovel2epub: IMG url fixed'); | |
}); | |
} | |
console.log('lightnovel2epub: zip loading'); | |
var epub = new JSZip(); | |
epub.file("mimetype", "application/epub+zip"); | |
epub.file("META-INF/container.xml", '<?xml version="1.0" encoding="UTF-8"?>\n<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">\n<rootfiles>\n<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>\n</rootfiles>\n</container>'); | |
epub.file("OEBPS/main.css", "img{\ndisplay: block;\nmargin: 0 auto;\n}"); | |
var opf = '<?xml version="1.0" encoding="UTF-8"?>\n<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uid">\n<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">\n<dc:identifier id="uid">{{uid}}</dc:identifier>\n<dc:title>{{title}}</dc:title>\n<dc:creator>{{creator}}</dc:creator>\n<dc:language>zh</dc:language>\n<meta property="dcterms:modified">{{time}}</meta>\n</metadata>\n<manifest>\n<item href="main.xhtml" id="main" media-type="application/xhtml+xml"/>\n<item href="nav.xhtml" id="nav" media-type="application/xhtml+xml" properties="nav"/>\n<item href="main.css" media-type="text/css" id="css"/>\n<item href="images/main_cover.jpeg" media-type="image/jpeg" id="cover" properties="cover-image"/>\n{{images}}</manifest>\n<spine>\n<itemref idref="main"/>\n<itemref idref="nav" linear="no"/>\n</spine>\n</package>'; | |
var opf_img = '<item href="{{srcepub}}" media-type="image/jpeg" id="{{file}}" />\n'; | |
var nav = '<?xml version="1.0" encoding="utf-8"?>\n<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n<head>\n<meta charset="utf-8" />\n<title>Table of Contents</title>\n<link rel="stylesheet" type="text/css" href="main.css" />\n</head>\n<body>\n<nav epub:type="toc" id="toc">\n<h1 class="title">Table of Contents</h1>\n<ol>\n<li id="main"><a href="main.xhtml">{{title}}</a></li>\n{{nav}}\n<li id="nav"><a href="nav.xhtml">Table of Contents</a></li>\n</ol>\n</nav>\n</body>\n</html>'; | |
var nav_p = '<li id="{{p}}"><a href="main.xhtml#{{p}}">{{h}}</a></li>\n'; | |
var xhtml = '<?xml version="1.0" encoding="utf-8"?>\n<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n<head>\n<meta charset="utf-8"/>\n<title>{{title}}</title>\n<link rel="stylesheet" type="text/css" href="main.css"/>\n</head>\n<body>\n{{main}}\n</body>\n</html>'; | |
var xhtml_h ='<h3 id="{{p}}">{{h}}</h3>\n'; | |
var [P_doc, doc_end] = Pstarter(); | |
var [P_img_s, img_start] = Pstarter(); | |
var [P_cover_s, cover_start] = Pstarter(); | |
var [P_img_e, img_end] = Pstarter(); | |
var [P_cover_e, cover_end] = Pstarter(); | |
var final_list = [P_doc,P_img_e,P_cover_e]; | |
var ss; | |
let Mf = (D,F) => { | |
console.log('lightnovel2epub: start! loading all page'); | |
ss = window.getSelection().toString().split('\n').map(e => e.trim()); | |
var pgl = []; | |
if (document.querySelector('.pg span')) { | |
let maxpg = Number(document.querySelector('.pg span').innerText.split(/[^\d]/).join('')); | |
let URL = document.documentURI.split(/&page=\d+&/).join('&'); | |
for (let i=1; i <= maxpg; i++){ | |
pgl.push({ | |
method: "GET", | |
url: URL+'&page='+i, | |
responseType: "document", | |
}); | |
} | |
} else { | |
let URL = document.documentURI.split(/&page=\d+&/).join('&'); | |
pgl = [{ | |
method: "GET", | |
url: URL, | |
responseType: "document", | |
}]; | |
}; | |
console.log('lightnovel2epub: document do same thin'); | |
Promise.all(pgl.map((e) => ajax(e))) | |
.then((e) => e.map((e) => [...e.target.responseXML.querySelectorAll('[id*=postmessage_], .pattl')])) | |
.then((e) => e.reduce((a, e) => a.concat(e),[]) | |
.filter((e) => !e.querySelector('.quote')) | |
.reduce((a, e) => a.concat([...e.childNodes]),[]) | |
.filter((e) => e.nodeName != 'I') | |
.map(f) | |
.reduce((a, e) => a.concat([...e.childNodes]),[]) | |
) | |
.then((e) => {img_start(e.filter(E=>E.nodeName === 'IMG').map(E=>E.cloneNode())); | |
console.log('lightnovel2epub: create epub img start'); | |
return e; | |
}) | |
.then((e) => e.map(e => {if(e.nodeName == 'IMG') e.removeAttribute('srclink'); return e.outerHTML;}) | |
.join('\n') | |
.split(/(?:<p><\/p>\n)+/).join('<p></p>\n') | |
.split('srcepub').join('src') | |
) | |
.then((End) => { | |
ss.forEach((e,i) => { | |
if (e) { | |
End = End.replace(RegExp('^((?:.|\n)*)<p>'+ e.split(/\s/).join('').split('').join('\\s*?') +'</p>\n((?:.|\n)*?)$'), '$1'+ xhtml_h.split('{{h}}').join(e).split('{{p}}').join('main'+i).split('\n').join('') +'\n$2'); | |
nav = nav.split('{{nav}}').join(nav_p.split('{{h}}').join(e).split('{{p}}').join('main'+i)+'{{nav}}'); | |
} | |
}); | |
End = End.replace(/^(<img.*?)>$/mg, '$1 />'); | |
return End; | |
}) | |
.then((End) => { | |
var tslt = document.querySelector('#thread_subject').innerText; | |
xhtml = xhtml | |
.split('{{main}}').join(End) | |
.split('{{title}}').join(tslt); | |
nav = nav | |
.split('{{title}}').join(tslt); | |
opf = opf | |
.split('{{time}}').join(new Date().toJSON()) | |
.split('{{uid}}').join('UID'+new Date().getTime()) | |
.split('{{title}}').join(tslt); | |
epub.file("OEBPS/main.xhtml", xhtml); | |
epub.file("OEBPS/nav.xhtml", nav.split('{{nav}}').join('')); | |
GM_setClipboard(End); | |
}) | |
.then(() => doc_end()); | |
}; | |
P_img_s.then((e) => { | |
Promise.all( | |
e.map((e1, i) => GM_ajax( | |
{ | |
method: "GET", | |
url: e1.getAttribute('srclink'), | |
responseType: "blob", | |
}) | |
.then(e => img_1(e).then(e => [e, e1])) | |
.catch(e => { | |
console.log('lightnovel2epub: img load error'); | |
return 'error img load'; | |
}) | |
) | |
) | |
.then(a => a.filter(E => E !== 'error')) | |
.then(a => { | |
var cover_ed = 0; | |
var tmp_end = a.filter(([e, E]) => { | |
if (Math.max(e.naturalWidth, e.naturalHeight) >= 600) { | |
if (!cover_ed) { | |
cover_start(e); | |
cover_ed = 1; | |
} | |
return true; | |
} else { | |
console.log('lightnovel2epub: img too small'); | |
return false; | |
} | |
}); | |
if (!tmp_end.length) { | |
console.log('lightnovel2epub: no img'); | |
throw 'error no img'; | |
} else return tmp_end; | |
}) | |
.then(a => a.map(([e, E]) => [img_2(e), E])) | |
.then(a => a.map(([b, e]) => { | |
var tmp_srcepub = e.getAttribute('srcepub'); | |
epub.file("OEBPS/"+tmp_srcepub, b); | |
let temp_img = opf_img | |
.split('{{srcepub}}').join(tmp_srcepub) | |
.split('{{file}}').join(tmp_srcepub); | |
opf = opf.split('{{images}}').join(temp_img+'{{images}}'); | |
})) | |
.catch(cover_end) | |
}) | |
.then(() => img_end()); | |
P_cover_s.then((timg) => new Promise((D, F) => { | |
console.log('lightnovel2epub: create epub cover img'); | |
if (timg.naturalWidth > timg.naturalHeight) { | |
// bakacrop.js start | |
var luma = (id, od) => { | |
for (let i=0; i < id.length; i+=4) { | |
od[i+1] = 0.21*id[i]+ 0.72*id[i+1]+ 0.07*id[i+2] | |
,od[i+3] = 255; | |
} | |
} | |
var sobel_edge = (p1, p2, p3 | |
,p4, p6 | |
,p7, p8, p9) => { | |
let Gy = p1+ 2*p2+ p3 | |
+ | |
-p7+ -2*p8+ -p9, | |
Gx = p1 + -p3+ | |
2*p4 + -2*p6+ | |
p7 + -p9; | |
return (Math.abs(Gx) + Math.abs(Gy))/6; | |
} | |
var sobel = (id,o) => { | |
var w4 = o.width*4 | |
,isN = Number.isInteger; | |
for (var l=0; l < o.height; l++) { | |
for (var x=0; x < o.width; x++) { | |
let p = l * w4 + x*4 +1; | |
let p1 = isN(id[p-w4-4]) ? id[p-w4-4] : 255 | |
,p2 = isN(id[p-w4 ]) ? id[p-w4 ] : 255 | |
,p3 = isN(id[p-w4+4]) ? id[p-w4+4] : 255 | |
,p4 = isN(id[p -4]) ? id[p -4] : 255 | |
,p6 = isN(id[p +4]) ? id[p +4] : 255 | |
,p7 = isN(id[p+w4-4]) ? id[p+w4-4] : 255 | |
,p8 = isN(id[p+w4 ]) ? id[p+w4 ] : 255 | |
,p9 = isN(id[p+w4+4]) ? id[p+w4+4] : 255; | |
o.data[p] = sobel_edge(p1, p2, p3 | |
,p4, p6 | |
,p7, p8, p9); | |
} | |
} | |
} | |
var tscanvas = document.createElement('canvas') | |
,ys = Math.max(~~(Math.max(timg.naturalWidth, timg.naturalHeight)/1080),2); | |
tscanvas.width = ~~(timg.naturalWidth /ys) | |
tscanvas.height = ~~(timg.naturalHeight /ys); | |
var tsC = tscanvas.getContext('2d'); | |
tsC.drawImage(timg, 0, 0, tscanvas.width, tscanvas.height); | |
var i = tsC.getImageData(0, 0, tscanvas.width, tscanvas.height) | |
,o = new ImageData(tscanvas.width, tscanvas.height); | |
var id = i.data | |
,od = o.data; | |
luma(id, od); | |
sobel(o.data.slice(), o); | |
tsC.putImageData(o, 0, 0); | |
var t = tsC.getImageData(0, 0, o.width, o.height) | |
,cw = ~~(o.height /1448*1072) | |
,s = [[], 0, []]; | |
for (var x=0; x < t.width; x++) { | |
var _ls = 0; | |
for (var l=0; l < t.height; l++) { | |
let p = (x + t.width * l)*4 +1; | |
_ls += t.data[p]; | |
} | |
s[1] += _ls | |
,s[0].unshift(_ls); | |
if (s[0].length === cw) { | |
s[2].push(s[1]) | |
,s[1] -= s[0].pop(); | |
} | |
} | |
var rx = s[2].indexOf(Math.max(...s[2])) * ys; | |
let tdcanvas = document.createElement('canvas'); | |
tdcanvas.width = ~~(timg.naturalHeight/1448*1072), | |
tdcanvas.height = timg.naturalHeight, | |
tdC = tdcanvas.getContext('2d'); | |
tdC.drawImage(timg, rx, 0, tdcanvas.width, tdcanvas.height, 0, 0, tdcanvas.width, tdcanvas.height); | |
// bakacrop.js end | |
tdcanvas.toBlob(D, 'image/jpeg', 1); | |
} else { | |
let tscanvas = document.createElement('canvas'); | |
tscanvas.width = timg.naturalWidth, | |
tscanvas.height = timg.naturalHeight; | |
tsC = tscanvas.getContext('2d'), | |
tsC.drawImage(timg, 0, 0), | |
tscanvas.toBlob(D, 'image/jpeg', 1); | |
} | |
}) | |
) | |
.then((b) => { | |
epub.file("OEBPS/images/main_cover.jpeg", b); | |
console.log('lightnovel2epub: create epub cover img end'); | |
cover_end(); | |
}) | |
.catch(e=>{ | |
console.log('lightnovel2epub: create epub cover img error'); | |
}); | |
Promise.all(final_list).then(v => { | |
epub.file("OEBPS/content.opf", opf.split('{{images}}').join('')); | |
console.log('lightnovel2epub: ziping'); | |
return epub.generateAsync({ | |
type: "blob", | |
mimeType: "application/epub+zip", | |
compression: "DEFLATE", | |
compressionOptions: {level: 9} | |
}); | |
}) | |
.then(u => { | |
console.log('lightnovel2epub: Done!'); | |
console.debug(u); | |
let fname = document.querySelector('#thread_subject').innerText+'.epub'; | |
var fhref = URL.createObjectURL(u); | |
document.querySelector('#thread_subject').outerHTML = '<a id="thread_subject" href="'+ fhref +'" download="'+ fname +'">'+ fname +'</a>'; | |
location.href += '#thread_subject'; | |
window.addEventListener("close", function(event) { | |
URL.revokeObjectURL(fhref); | |
}, false); | |
}); | |
GM_registerMenuCommand('lightnovel2epub: Run', Mf); | |
console.log('lightnovel2epub: JSZip.version='+JSZip.version); | |
//console.debug(GM_ajax); | |
//console.debug(final_list); | |
//P_img_s.then((e) => console.debug(e)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment