-
-
Save smartchaos/fcf95f4ebe21059d5eb5241bef87f5c1 to your computer and use it in GitHub Desktop.
Kindle 中国 批量下载自己的电子书和个人文档
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
| /** | |
| * 批量下载自己已购买的电子书和个人文档 | |
| * 要求:至少有一台Kindle设备。 | |
| * | |
| * 打开 https://z.cn/myk ,然后按F12键进入Console(控制台),把代码全部复制并粘贴到控制台中,回车。 | |
| * 然后输入 download("ebook") ,下载所有的电子书 | |
| * 想下载个人文档,则是输入 download("pdoc") | |
| * 下载时如果某个文件下载失败,可以使用刚刚运行的函数(也就是 download() 或者 download("pdoc") )重新开始下载。在网页没被关闭的情况下,程序会忽略已经下载了的文件。 | |
| * 程序顺利完成的情况下,会打开一个新页面,其中时该次下载任务中成功下载的文件列表。 | |
| * 脚本运行期间请不要关闭网页,请允许网页自动下载多个文件 | |
| * 如果网页被关闭了,但恰巧你保存了上次下载任务返回的成功下载的文件列表, | |
| * 可以选择复制该列表中的所有文字,并将其作为 download 的第二个参数传入(如 download("ebook",["something","something else"]) ),这样程序同样会忽略已经下载了的文件。 | |
| * | |
| * 原来通过请求获取下载url的方法只适用于电子书,虽然有意识到下载链接似乎有一定规律,但也没多想, | |
| * 后来看到 https://github.com/yihong0618/Kindle_download_helper 这个项目,发现他是用拼接url而非请求获取url, | |
| * 这样可以获取个人文档的下载链接,于是研究了下拼接用的参数,现在这个脚本也能下载个人文档了 | |
| * 超过1000本书的情况,也是借鉴了该项目的处理方法,因为本人没那么多书,所以==v== | |
| */ | |
| function decodeEntity(inputStr) { | |
| var textarea = document.createElement("textarea"); | |
| textarea.innerHTML = inputStr; | |
| return textarea.value; | |
| } | |
| async function ajax(data) { | |
| function obj2Urlencoded(obj) { | |
| const arr = []; | |
| for (const key in obj) arr.push(`${key}=${obj[key]}`); | |
| return arr.join("&"); | |
| }; | |
| const res = await fetch("https://www.amazon.cn/hz/mycd/digital-console/ajax", { | |
| headers: { | |
| "content-type": "application/x-www-form-urlencoded", | |
| }, | |
| body: obj2Urlencoded({ | |
| data: JSON.stringify(data), | |
| csrfToken: encodeURIComponent(window.csrfToken) | |
| }), | |
| method: "POST", | |
| credentials: "include" | |
| }); | |
| return await res.json() || {}; | |
| }; | |
| /** | |
| * @param type 默认为 "ebook" ,下载电子书,若为 "pdoc", 则是下载个人文档 | |
| * @param completedList 数组,如果一本电子书的asin在其中就不会下载 | |
| */ | |
| async function download(type = "ebook", completedList = window.completedList || []) { | |
| function getCompletedList(list = []) { | |
| window.open(URL.createObjectURL(new Blob([JSON.stringify(list)], { | |
| type: "application/json" | |
| }))); | |
| }; | |
| async function dl(url, fileName) { | |
| try { | |
| console.log("开始下载", fileName); | |
| const startAt = new Date(); | |
| const res = await fetch(url, { | |
| credentials: "include" | |
| }); | |
| const contentLength = res.headers.get('content-length') / 1024 / 1024; | |
| const reader = res.body.getReader(); | |
| let receivedLength = 0; | |
| let chunks = []; | |
| while (true) { | |
| const { | |
| done, | |
| value | |
| } = await reader.read(); | |
| if (done) break; | |
| chunks.push(value); | |
| receivedLength += value.length; | |
| }; | |
| const blob = new Blob(chunks, { | |
| type: res.headers.get('content-type') | |
| }); | |
| let a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = fileName || "未命名文件"; | |
| document.body.append(a); | |
| a.click(); | |
| console.log(`下载 ${fileName} 完成!文件大小共${contentLength.toFixed(2)}MB,下载耗时 ${(new Date()-startAt)/1e3} s`); | |
| a.remove(); | |
| } catch (err) { | |
| console.error("Download Error:", err); | |
| throw { | |
| url: url, | |
| fileName: fileName | |
| } | |
| }; | |
| }; | |
| const devices = await ajax({ | |
| param: { | |
| GetDevices: {} | |
| } | |
| }).catch(err => { | |
| console.error("获取设备列表失败:", err); | |
| throw err; | |
| }); | |
| const newCompletedList = [...completedList]; | |
| const params = { | |
| OwnershipData: { | |
| sortOrder: "DESCENDING", | |
| sortIndex: "DATE", | |
| batchSize: 1000, | |
| totalContentCount: 0, | |
| itemStatus: ["Active"] | |
| } | |
| }; | |
| var startIndex = 0; | |
| if (!devices.GetDevices.devices.length) { | |
| return console.warn("没有可供下载的设备,必须要有至少一台Kindle设备才可以下载……"); | |
| }; | |
| if (type == "pdoc") { | |
| params.OwnershipData = Object.assign(params.OwnershipData, { | |
| contentType: "KindlePDoc", | |
| itemStatus: ["Active"] | |
| }); | |
| } else { | |
| params.OwnershipData = Object.assign(params.OwnershipData, { | |
| contentType: "Ebook", | |
| itemStatus: ["Active", "Expired"], | |
| excludeExpiredItemsFor: ["KOLL", "Purchase", "Pottermore", "FreeTrial", "DeviceRegistration", "ku", "Sample", "Prime", "ComicsUnlimited"], | |
| originType: ["Purchase", "PublicLibraryLending", "PersonalLending", "ComicsUnlimited", "KOLL", "RFFLending", "Pottermore", "Rental", "DeviceRegistration", "FreeTrial", "ku", "Sample", "Prime"], | |
| showSharedContent: true | |
| }); | |
| }; | |
| while (true) { | |
| params.OwnershipData = Object.assign(params.OwnershipData, { | |
| startIndex: startIndex, | |
| }); | |
| const res = await ajax({ | |
| param: params | |
| }).catch(err => { | |
| console.error("获取电子书列表失败:", err); | |
| throw err; | |
| }); | |
| for (let eBook of res.OwnershipData.items) { | |
| const docType = (type == "pdoc") ? "PDOC" : "EBOK", | |
| key = eBook.asin, | |
| fsn = devices.GetDevices.devices[0].deviceSerialNumber, | |
| deviceType = devices.GetDevices.devices[0].deviceType, | |
| customerId = devices.GetDevices.devices[0].customerId; | |
| try { | |
| if (newCompletedList.includes(key)) continue; | |
| await dl(`https://cde-ta-g7g.amazon.com/FionaCDEServiceEngine/FSDownloadContent?type=${docType}&key=${key}&fsn=${fsn}&device_type=${deviceType}&customerId=${customerId}&authPool=AmazonCN`, | |
| `${decodeEntity(eBook.authors ||eBook.author)} - ${decodeEntity(eBook.title)}.azw3`); | |
| newCompletedList.push(key); | |
| } catch (err) { | |
| console.warn("下载电子书失败:", err); | |
| }; | |
| }; | |
| if (!res.hasMoreItems) break; | |
| startIndex += 1000; | |
| }; | |
| console.log("Kindle 设备序列号:",devices.GetDevices.devices[0].deviceSerialNumber,"可以用于为下载的电子书移除DRM。个人文档无需去除DRM"); | |
| window.completedList = newCompletedList; | |
| getCompletedList(newCompletedList); | |
| console.log("刚刚打开的网页,是本次下载任务中,已经完成下载的电子书的数据。将其作为 download 函数的第二个参数传入,则该次下载任务会忽略这些已下载的电子书。"); | |
| console.log("任务结束……"); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment