-
-
Save myd7349/2854017c449d2e5ab53c5cb935090b3c to your computer and use it in GitHub Desktop.
weread download,生成kindlegen可用的源文件,可用来生成mobi
This file contains hidden or 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 微信读书下载 | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.1 | |
| // @description 下载微信读书的书籍资源 | |
| // @author You | |
| // @match https://weread.qq.com/web/reader/* | |
| // @grant unsafeWindow | |
| // @grant GM_setValue | |
| // @grant GM_getValue | |
| // @grant GM_addScript | |
| // @run-at document-idle | |
| // @require https://cdn.bootcss.com/jszip/3.2.2/jszip.js | |
| // @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js | |
| // @require https://unpkg.com/art-template/lib/template-web.js | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| class Ebook { | |
| constructor(id, title, author, intro, publisher, publishTime, maxLevel) { | |
| this.id = id; | |
| this.title = title; | |
| this.author = author; | |
| this.intro = intro; | |
| this.publisher = publisher; | |
| this.publishTime = publishTime; | |
| this.maxLevel = maxLevel; | |
| }; | |
| setCpid(cpid) { | |
| this.cpid = cpid; | |
| } | |
| setIsbn(isbn) { | |
| this.isbn = isbn; | |
| } | |
| setChapterList(chapterList) { | |
| this.chapterList = chapterList; | |
| } | |
| } | |
| class Chapter { | |
| constructor(uid, path, title, level, playOrder) { | |
| this.uid = uid; | |
| this.path = path; | |
| this.title = title; | |
| this.level = level; | |
| this.playOrder = playOrder; | |
| this.subChapter = []; | |
| }; | |
| addSubChapter(chapter) { | |
| this.subChapter.push(chapter) | |
| } | |
| getLastSubChapter() { | |
| return this.subChapter[this.subChapter.length - 1] | |
| } | |
| } | |
| const buildEbook = book => { | |
| var maxLevel = 1 | |
| var chapterList = [] | |
| var prveFirstLevelChapter | |
| book.chapterInfos.forEach((element, i) => { | |
| var chapter = new Chapter(element.chapterIdx, element.chapterIdx + ".html", element.title, element.level, i + 1) | |
| if (chapter.level > maxLevel) { | |
| maxLevel = chapter.level | |
| } | |
| if (chapter.level == 1) { | |
| chapterList.push(chapter) | |
| prveFirstLevelChapter = chapter | |
| } else if (chapter.level == 2) { | |
| prveFirstLevelChapter.addSubChapter(chapter) | |
| } else if (chapter.level == 3) { | |
| if(prveFirstLevelChapter.getLastSubChapter()==undefined){ | |
| prveFirstLevelChapter.addSubChapter(chapter) | |
| }else{ | |
| prveFirstLevelChapter.getLastSubChapter().addSubChapter(chapter) | |
| } | |
| } else if (chapter.level == 4) { | |
| if(prveFirstLevelChapter.getLastSubChapter().getLastSubChapter()==undefined){ | |
| prveFirstLevelChapter.getLastSubChapter().addSubChapter(chapter) | |
| }else{ | |
| prveFirstLevelChapter.getLastSubChapter().getLastSubChapter().addSubChapter(chapter) | |
| } | |
| } else { | |
| alert("暂不支持五级目录深度 " + chapter.level) | |
| return | |
| } | |
| }); | |
| var ebook = new Ebook( | |
| book.bookInfo.bookId, | |
| book.bookInfo.title, | |
| book.bookInfo.author, | |
| book.bookInfo.intro, | |
| book.bookInfo.publisher, | |
| book.bookInfo.publishTime, | |
| maxLevel | |
| ) | |
| ebook.setChapterList(chapterList) | |
| ebook.setIsbn(book.bookInfo.isbn) | |
| ebook.setCpid(book.bookInfo.cpid) | |
| return ebook | |
| } | |
| const sleep = ms => { | |
| return new Promise(resolve => | |
| setTimeout(resolve, ms) | |
| ) | |
| } | |
| function createAndDownloadFile(fileName, content) { | |
| var aTag = document.createElement('a'); | |
| aTag.download = fileName; | |
| aTag.href = URL.createObjectURL(content); | |
| aTag.click(); | |
| URL.revokeObjectURL(content); | |
| } | |
| const imageUrlToBlob = url => fetch(url) | |
| .then(response => response.blob()) | |
| function uploadFileConvertToMobi(filename, fileContent) { | |
| const formData = new FormData(); | |
| formData.append('originFile', fileContent); | |
| fetch('https://mobi.ibeta.live/kindle/generate', { | |
| method: 'post', | |
| body: formData, | |
| }).then(response => response.blob()) | |
| .then(data => { | |
| unsafeWindow.mobiBook = data | |
| createAndDownloadFile(filename + ".mobi", data) | |
| }) | |
| .catch(error => { | |
| console.log(error) | |
| }); | |
| } | |
| //var $ = unsafeWindow.$ | |
| var vue = $("div.readerContent.routerView")[0] | |
| // Your code here... | |
| //book = unsafeWindow.$("div.readerContent.routerView").__vue__ | |
| const parseCss = cssText => cssText.replace(/\.readerChapterContent/g, "") | |
| const fixBody = failBody => failBody.replace("</body>", "</div>") | |
| const buildHead = book => `<title>${book.currentChapter.title}</title> | |
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">` | |
| const buildHtml = (head, body, css) => { | |
| var html = `<html><head>${head}</head><body><style>${css}</style>${body}</body></html>` | |
| return html | |
| } | |
| function cleanAttr(element) { | |
| element.removeAttr("data-wr-co") | |
| element.removeAttr("data-wr-bd") | |
| element.removeAttr("data-wr-id") | |
| element.removeAttr("data-ratio") | |
| element.removeAttr("data-w") | |
| element.removeAttr("data-w-new") | |
| element.removeData("wr-co") | |
| element.removeData("wr-bd") | |
| element.removeData("wr-id") | |
| element.removeData("ratio") | |
| element.removeData("w") | |
| element.removeData("w-new") | |
| } | |
| function cleanTag(element) { | |
| cleanAttr(element) | |
| element.html(element.text()) | |
| } | |
| const log = str => { | |
| $('.readerMemberCardTips').attr("style", "") | |
| $('.readerMemberCardTips > .text').html(str) | |
| } | |
| const replaceImages = (doc, zip) => { | |
| doc.find("img") | |
| .each(function () { | |
| var img = $(this) | |
| var url = img.attr("data-src"); | |
| console.log("处理图片 " + url) | |
| if (url.indexOf("http") == -1) return | |
| var imageName = url.substr(url.lastIndexOf("/") + 1) | |
| if (imageName.indexOf(".") == -1) imageName += ".jpg" | |
| zip.file("img/" + imageName, imageUrlToBlob(url)) | |
| img.attr("src", "img/" + imageName); | |
| img.removeAttr("data-src") | |
| }) | |
| return doc.html() | |
| } | |
| const cleanHtml = doc => { | |
| doc.find("div").each(function () { | |
| cleanAttr($(this)) | |
| }) | |
| doc.find("img").each(function () { | |
| cleanAttr($(this)) | |
| }) | |
| doc.find("h1").each(function () { | |
| cleanTag($(this)) | |
| }) | |
| doc.find("h2").each(function () { | |
| cleanTag($(this)) | |
| }) | |
| doc.find("h3").each(function () { | |
| cleanTag($(this)) | |
| }) | |
| doc.find("p").each(function () { | |
| if ($(this).find("img").length > 0) { | |
| cleanAttr($(this)) | |
| } else { | |
| cleanTag($(this)) | |
| } | |
| }) | |
| } | |
| var tocncx = ['<?xml version="1.0" encoding="UTF-8"?>', | |
| '<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">', | |
| '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en-US">', | |
| ' <head>', | |
| ' <meta name="dtb:uid" content="{{book.id}}"/>', | |
| ' <meta name="dtb:depth" content="{{book.maxLevel}}"/>', | |
| ' <meta name="dtb:totalPageCount" content="0"/>', | |
| ' <meta name="dtb:maxPageNumber" content="0"/>', | |
| ' </head>', | |
| ' <docTitle>', | |
| ' <text>{{ book.title }}</text>', | |
| ' </docTitle>', | |
| ' <docAuthor>', | |
| ' <text>{{ book.author }}</text>', | |
| ' </docAuthor>', | |
| ' <navMap>', | |
| ' <navPoint class="toc" id="toc" playOrder="1">', | |
| ' <navLabel>', | |
| ' <text>Table of Contents</text>', | |
| ' </navLabel>', | |
| ' <content src="toc.html"/>', | |
| ' </navPoint>', | |
| ' {{each book.chapterList}}', | |
| ' <navPoint class="chapter" id="chapter_{{$value.uid}}" playOrder="{{$value.playOrder}}">', | |
| ' <navLabel>', | |
| ' <text>{{$value.title}}</text>', | |
| ' </navLabel>', | |
| ' <content src="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <navPoint class="chapter" id="chapter_{{$value.uid}}" playOrder="{{$value.playOrder}}">', | |
| ' <navLabel>', | |
| ' <text>{{$value.title}}</text>', | |
| ' </navLabel>', | |
| ' <content src="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <navPoint class="chapter" id="chapter_{{$value.uid}}" playOrder="{{$value.playOrder}}">', | |
| ' <navLabel>', | |
| ' <text>{{$value.title}}</text>', | |
| ' </navLabel>', | |
| ' <content src="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <navPoint class="chapter" id="chapter_{{$value.uid}}" playOrder="{{$value.playOrder}}">', | |
| ' <navLabel>', | |
| ' <text>{{$value.title}}</text>', | |
| ' </navLabel>', | |
| ' <content src="{{$value.path}}"/>', | |
| ' </navPoint>', | |
| ' {{/each}}', | |
| ' </navPoint>', | |
| ' {{/each}}', | |
| ' </navPoint>', | |
| ' {{/each}}', | |
| ' </navPoint>', | |
| ' {{/each}}', | |
| ' </navMap>', | |
| '</ncx>'].join(""); | |
| var tochtml = ['<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">', | |
| '<html xmlns="http://www.w3.org/1999/xhtml">', | |
| '<head>', | |
| ' <title>Table of Contents</title>', | |
| ' <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>', | |
| '</head>', | |
| '<body>', | |
| '<h1><b>TABLE OF CONTENTS</b></h1>', | |
| '<br/>', | |
| '{{each book.chapterList}}', | |
| '<h3><b><a href="{{$value.path}}">{{$value.title}}</a></b></h3>', | |
| '<ul>', | |
| ' {{each $value.subChapter}}', | |
| ' <li><a href="{{$value.path}}">{{$value.title}}</a></li>', | |
| ' {{if $value.subChapter}}', | |
| ' <ul>', | |
| ' {{each $value.subChapter}}', | |
| ' <li><a href="{{$value.path}}">{{$value.title}}</a></li>', | |
| ' {{if $value.subChapter}}', | |
| ' <ul>', | |
| ' {{each $value.subChapter}}', | |
| ' <li><a href="{{$value.path}}">{{$value.title}}</a></li>', | |
| ' {{/each}}', | |
| ' </ul>', | |
| ' {{/if}}', | |
| ' {{/each}}', | |
| ' </ul>', | |
| ' {{/if}}', | |
| ' {{/each}}', | |
| '</ul>', | |
| '{{/each}}', | |
| '</body>', | |
| '</html>', | |
| ].join(""); | |
| var opf_tmp = ['<?xml version="1.0" encoding="utf-8"?>', | |
| '<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="BookId">', | |
| ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">', | |
| ' <dc:title>{{ book.title }}</dc:title>', | |
| ' <dc:language>zh-cn</dc:language>', | |
| ' <dc:creator>{{ book.author }}</dc:creator>', | |
| ' {{if book.intro}}', | |
| ' <dc:description><div>', | |
| ' <p>{{book.intro}}</p></div>', | |
| ' </dc:description>', | |
| ' {{/if}}', | |
| ' {{if book.publisher}}', | |
| ' <dc:publisher>{{book.publisher}}</dc:publisher>', | |
| ' {{/if}}', | |
| ' {{if book.publishTime}}', | |
| ' <dc:date>{{book.publishTime}}</dc:date>', | |
| ' {{/if}}', | |
| ' {{if book.isbn}}', | |
| ' <dc:identifier opf:scheme="ISBN">{{book.isbn}}</dc:identifier>', | |
| ' {{/if}}', | |
| ' {{if book.cpid}}', | |
| ' <dc:identifier opf:scheme="CPID">{{book.cpid}}</dc:identifier>', | |
| ' {{/if}}', | |
| ' <meta name="cover" content="cover_image"/>', | |
| ' </metadata>', | |
| ' <manifest>', | |
| ' <item id="cover_image" href="cover.jpg" media-type="image/jpeg"/>', | |
| ' <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx"/>', | |
| ' <item id="toc_html" media-type="application/xhtml+xml" href="toc.html"/>', | |
| ' {{each book.chapterList}}', | |
| ' <item id="chapter_{{$value.uid}}" media-type="application/xhtml+xml" href="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <item id="chapter_{{$value.uid}}" media-type="application/xhtml+xml" href="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <item id="chapter_{{$value.uid}}" media-type="application/xhtml+xml" href="{{$value.path}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <item id="chapter_{{$value.uid}}" media-type="application/xhtml+xml" href="{{$value.path}}"/>', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' </manifest>', | |
| ' <spine toc="toc">', | |
| ' <itemref idref="toc_html"/>', | |
| ' {{each chapterList}}', | |
| ' <itemref idref="chapter_{{$value.uid}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <itemref idref="chapter_{{$value.uid}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <itemref idref="chapter_{{$value.uid}}"/>', | |
| ' {{each $value.subChapter}}', | |
| ' <itemref idref="chapter_{{$value.uid}}"/>', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' {{/each}}', | |
| ' </spine>', | |
| ' <guide>', | |
| ' <reference type="toc" title="Table of Contents" href="toc.html"/>', | |
| ' <reference type="text" title="{{book.chapterList[0].title}}" href="{{book.chapterList[0].path}}"/>', | |
| ' </guide>', | |
| '</package>', | |
| ].join(""); | |
| var addToc = (book, zip) => { | |
| var toc = book.bookInfo.title | |
| book.chapterInfos.forEach(element => { | |
| var levelStr = "#".repeat(element.level) | |
| toc += "\n" + levelStr + " " + element.title | |
| }); | |
| log("addToc") | |
| console.log(toc) | |
| zip.file("toc.md", toc); | |
| var ebook = buildEbook(book) | |
| zip.file("toc.ncx", template.render(tocncx, { "book": ebook })); | |
| zip.file("toc.html", template.render(tochtml, { "book": ebook })); | |
| zip.file(ebook.title + ".opf", template.render(opf_tmp, { "book": ebook })); | |
| } | |
| var addInfo = (book, zip) => { | |
| log("addInfo") | |
| book.bookInfo.cover = $('img.wr_bookCover_img').attr("src") | |
| zip.file("bookInfo.json", JSON.stringify(book.bookInfo)); | |
| zip.file("chapterInfos.json", JSON.stringify(book.chapterInfos)); | |
| zip.file("readme.txt", "使用kindlegen生成电子书,执行命令:\nkindlegen -dont_append_source " + book.bookInfo.title + ".opf"); | |
| } | |
| var addCover = (book, zip) => { | |
| log("addCover") | |
| book.bookInfo.cover = $('img.wr_bookCover_img').attr("src") | |
| zip.file("cover.jpg", imageUrlToBlob(book.bookInfo.cover)); | |
| } | |
| var count = 0 | |
| var addChapter = (book, zip) => { | |
| log("正在下载数据 " + (count + 1) + "/" + book.chapterInfos.length + " : " + book.currentChapter.title) | |
| var head = buildHead(book) | |
| var rawBody = $(fixBody(book.chapterContentForEPub.toString())) | |
| cleanHtml(rawBody) | |
| var body = replaceImages(rawBody, zip) | |
| var newHtml = buildHtml(head, body, parseCss(book.chapterContentStyles)); | |
| zip.file(book.currentChapter.chapterIdx + ".html", newHtml); | |
| count++ | |
| } | |
| var download = (book, zip) => { | |
| if (count >= book.chapterInfos.length) { | |
| // if (count >= 4) { | |
| zip.generateAsync({ type: "blob" }) | |
| .then(function (content) { | |
| unsafeWindow.rawBook = content | |
| log('已获取全部数据,点击<a href="javascript:" title="下载" class="click_download">下载</a>,在线生成Mobi文件<a href="javascript:" title="下载" class="online_to_mobi">点击</a>') | |
| $(".click_download").click(function () { | |
| if (unsafeWindow.rawBook) { | |
| createAndDownloadFile(book.bookInfo.title + ".zip", unsafeWindow.rawBook); | |
| } else { | |
| log("缺失文件,请重新下载") | |
| } | |
| }) | |
| $(".online_to_mobi").click(function () { | |
| if (!unsafeWindow.rawBook) { | |
| log("缺失文件,请重新下载") | |
| return | |
| } | |
| if (unsafeWindow.mobiBook) { | |
| createAndDownloadFile(book.bookInfo.title + ".mobi", unsafeWindow.mobiBook); | |
| } | |
| uploadFileConvertToMobi(book.bookInfo.title, unsafeWindow.rawBook) | |
| }) | |
| $(".click_download").click() | |
| }); | |
| return | |
| } | |
| sleep(3000).then(() => { | |
| book.handleNextChapter().then(() => { | |
| addChapter(book, zip) | |
| download(book, zip) | |
| }); | |
| }) | |
| } | |
| sleep(5000).then(() => { | |
| var book = vue.__vue__ | |
| unsafeWindow.book = book | |
| var downloadBtn = '<button title="下载" class="readerControls_item download1"><span class="icon" style="background-image: url(https://www.easyicon.net/download/svg/1229320/598/);"></span></button>'; | |
| $('button.catalog').after(downloadBtn); | |
| $(".download1").click(function () { | |
| if (!book.isEPub) { | |
| alert("该书源非EPUB,暂不支持下载!") | |
| } | |
| var zip = new JSZip(); | |
| unsafeWindow.$zip = zip | |
| addInfo(book, zip) | |
| addToc(book, zip) | |
| addCover(book, zip) | |
| book.handleClickFirstChapter().then(() => { | |
| addChapter(book, zip) | |
| download(book, zip) | |
| }) | |
| }) | |
| console.log("微信读书下载插件已加载!") | |
| console.log(buildEbook(book)) | |
| }) | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment