You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
130 KiB
265 lines
130 KiB
2 years ago
|
/*
|
||
|
* FileSaver.js
|
||
|
* A saveAs() FileSaver implementation.
|
||
|
*
|
||
|
* By Eli Grey, http://eligrey.com
|
||
|
*
|
||
|
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
|
||
|
* source : http://purl.eligrey.com/github/FileSaver.js
|
||
|
*/
|
||
|
// The one and only way of getting global scope in all environments
|
||
|
// https://stackoverflow.com/q/3277182/1008999
|
||
|
(function (a, b) { if ("function" == typeof define && define.amd) define([], b); else if ("undefined" != typeof exports) b(); else { b(), a.FileSaver = { exports: {} }.exports } })(this, function () { "use strict"; function b(a, b) { return "undefined" == typeof b ? b = { autoBom: !1 } : "object" != typeof b && (console.warn("Deprecated: Expected third argument to be a object"), b = { autoBom: !b }), b.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type) ? new Blob(["\uFEFF", a], { type: a.type }) : a } function c(a, b, c) { var d = new XMLHttpRequest; d.open("GET", a), d.responseType = "blob", d.onload = function () { g(d.response, b, c) }, d.onerror = function () { console.error("could not download file") }, d.send() } function d(a) { var b = new XMLHttpRequest; b.open("HEAD", a, !1); try { b.send() } catch (a) { } return 200 <= b.status && 299 >= b.status } function e(a) { try { a.dispatchEvent(new MouseEvent("click")) } catch (c) { var b = document.createEvent("MouseEvents"); b.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), a.dispatchEvent(b) } } var f = "object" == typeof window && window.window === window ? window : "object" == typeof self && self.self === self ? self : "object" == typeof global && global.global === global ? global : void 0, a = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent), g = f.saveAs || ("object" != typeof window || window !== f ? function () { } : "download" in HTMLAnchorElement.prototype && !a ? function (b, g, h) { var i = f.URL || f.webkitURL, j = document.createElement("a"); g = g || b.name || "download", j.download = g, j.rel = "noopener", "string" == typeof b ? (j.href = b, j.origin === location.origin ? e(j) : d(j.href) ? c(b, g, h) : e(j, j.target = "_blank")) : (j.href = i.createObjectURL(b), setTimeout(function () { i.revokeObjectURL(j.href) }, 4E4), setTimeout(function () { e(j) }, 0)) } : "msSaveOrOpenBlob" in navigator ? function (f, g, h) { if (g = g || f.name || "download", "string" != typeof f) navigator.msSaveOrOpenBlob(b(f, h), g); else if (d(f)) c(f, g, h); else { var i = document.createElement("a"); i.href = f, i.target = "_blank", setTimeout(function () { e(i) }) } } : function (b, d, e, g) { if (g = g || open("", "_blank"), g && (g.document.title = g.document.body.innerText = "downloading..."), "string" == typeof b) return c(b, d, e); var h = "application/octet-stream" === b.type, i = /constructor/i.test(f.HTMLElement) || f.safari, j = /CriOS\/[\d]+/.test(navigator.userAgent); if ((j || h && i || a) && "undefined" != typeof FileReader) { var k = new FileReader; k.onloadend = function () { var a = k.result; a = j ? a : a.replace(/^data:[^;]*;/, "data:attachment/file;"), g ? g.location.href = a : location = a, g = null }, k.readAsDataURL(b) } else { var l = f.URL || f.webkitURL, m = l.createObjectURL(b); g ? g.location = m : location.href = m, g = null, setTimeout(function () { l.revokeObjectURL(m) }, 4E4) } }); f.saveAs = g.saveAs = g, "undefined" != typeof module && (module.exports = g) });
|
||
|
|
||
|
//# sourceMappingURL=FileSaver.min.js.map
|
||
|
|
||
|
|
||
|
/*!
|
||
|
|
||
|
JSZip v3.10.0 - A JavaScript class for generating and reading zip files
|
||
|
<http://stuartk.com/jszip>
|
||
|
|
||
|
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||
|
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||
|
|
||
|
JSZip uses the library pako released under the MIT license :
|
||
|
https://github.com/nodeca/pako/blob/main/LICENSE
|
||
|
*/
|
||
|
|
||
|
!function (e) { if ("object" == typeof exports && "undefined" != typeof module) module.exports = e(); else if ("function" == typeof define && define.amd) define([], e); else { ("undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : this).JSZip = e() } }(function () { return function s(a, o, h) { function u(r, e) { if (!o[r]) { if (!a[r]) { var t = "function" == typeof require && require; if (!e && t) return t(r, !0); if (l) return l(r, !0); var n = new Error("Cannot find module '" + r + "'"); throw n.code = "MODULE_NOT_FOUND", n } var i = o[r] = { exports: {} }; a[r][0].call(i.exports, function (e) { var t = a[r][1][e]; return u(t || e) }, i, i.exports, s, a, o, h) } return o[r].exports } for (var l = "function" == typeof require && require, e = 0; e < h.length; e++)u(h[e]); return u }({ 1: [function (e, t, r) { "use strict"; var d = e("./utils"), c = e("./support"), p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; r.encode = function (e) { for (var t, r, n, i, s, a, o, h = [], u = 0, l = e.length, f = l, c = "string" !== d.getTypeOf(e); u < e.length;)f = l - u, n = c ? (t = e[u++], r = u < l ? e[u++] : 0, u < l ? e[u++] : 0) : (t = e.charCodeAt(u++), r = u < l ? e.charCodeAt(u++) : 0, u < l ? e.charCodeAt(u++) : 0), i = t >> 2, s = (3 & t) << 4 | r >> 4, a = 1 < f ? (15 & r) << 2 | n >> 6 : 64, o = 2 < f ? 63 & n : 64, h.push(p.charAt(i) + p.charAt(s) + p.charAt(a) + p.charAt(o)); return h.join("") }, r.decode = function (e) { var t, r, n, i, s, a, o = 0, h = 0, u = "data:"; if (e.substr(0, u.length) === u) throw new Error("Invalid base64 input, it looks like a data url."); var l, f = 3 * (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, "")).length / 4; if (e.charAt(e.length - 1) === p.charAt(64) && f--, e.charAt(e.length - 2) === p.charAt(64) && f--, f % 1 != 0) throw new Error("Invalid base64 input, bad content length."); for (l = c.uint8array ? new Uint8Array(0 | f) : new Array(0 | f); o < e.length;)t = p.indexOf(e.charAt(o++)) << 2 | (i = p.indexOf(e.charAt(o++))) >> 4, r = (15 & i) << 4 | (s = p.indexOf(e.charAt(o++))) >> 2, n = (3 & s) << 6 | (a = p.indexOf(e.charAt(o++))), l[h++] = t, 64 !== s && (l[h++] = r), 64 !== a && (l[h++] = n); return l } }, { "./support": 30, "./utils": 32 }], 2: [function (e, t, r) { "use strict"; var n = e("./external"), i = e("./stream/DataWorker"), s = e("./stream/Crc32Probe"), a = e("./stream/DataLengthProbe"); function o(e, t, r, n, i) { this.compressedSize = e, this.uncompressedSize = t, this.crc32 = r, this.compression = n, this.compressedContent = i } o.prototype = { getContentWorker: function () { var e = new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")), t = this; return e.on("end", function () { if (this.streamInfo.data_length !== t.uncompressedSize) throw new Error("Bug : uncompressed data size mismatch") }), e }, getCompressedWorker: function () { return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize", this.compressedSize).withStreamInfo("uncompressedSize", this.uncompressedSize).withStreamInfo("crc32", this.crc32).withStreamInfo("compression", this.compression) } }, o.createWorkerFrom = function (e, t, r) { return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression", t) }, t.exports = o }, { "./external": 6, "./stream/Crc32Probe": 25, "./stream/DataLengthProbe": 26, "./stream/DataWorker": 27 }], 3: [function (e, t, r) { "use strict"; var n = e("./stream/GenericWorker"); r.STORE = { magic: "\0\0", compressWorker: function (e) { return new n("STORE compression") }, uncompressWorker: function () { return new n("STORE decompression") } }, r.DEFLATE = e("./flate") }, { "./flate": 7, "./stream/GenericWorker": 28 }], 4: [function (e, t, r) { "use strict"; var n = e("./utils"); var o = function () { for (var e, t = [], r = 0; r < 256; r++) { e = r; for (var n = 0; n < 8; n++)e = 1 & e ? 3988292384 ^ e >>> 1 : e >>> 1; t[r] = e } re
|
||
|
|
||
|
alert('扩展程序使用方法:右击漫画封面-选择下载漫画。\n扩展程序置顶,单击即可查看漫画下载进度。')
|
||
|
|
||
|
let popover = document.body.querySelector('._1L3sdSZDIqnTIjujeIEWJ6')
|
||
|
console.debug(popover)
|
||
|
let recentlyPurchasedCarousel = document.body.querySelector('#recentlyPurchasedCarousel')
|
||
|
console.debug(recentlyPurchasedCarousel)
|
||
|
|
||
|
let itemViewResponse = JSON.parse(document.querySelector('#itemViewResponse').innerText)
|
||
|
console.debug(itemViewResponse)
|
||
|
|
||
|
let side_bar_filters = document.querySelector('#side_bar_filters')
|
||
|
|
||
|
let popoverObserver, mangalistObserver
|
||
|
// 观察器的配置(需要观察什么变动)
|
||
|
const config = { childList: true };
|
||
|
|
||
|
let currentBook = {}
|
||
|
|
||
|
// 公共请求头
|
||
|
function createHeader() {
|
||
|
return {
|
||
|
"Accept-Language": navigator.language || "en-US",
|
||
|
"X-ADP-AttemptCount": "1",
|
||
|
"X-ADP-CorrelationId": (c = function () {
|
||
|
return (65536 * (1 + Math.random()) | 0).toString(16).substring(1)
|
||
|
}
|
||
|
,
|
||
|
c() + c() + "-" + c() + "-" + c() + "-" + c() + "-" + c() + c() + c()),
|
||
|
"X-ADP-Reason": "DevicePurchase",
|
||
|
"X-ADP-SW": "1170760200",
|
||
|
"X-ADP-Transport": "WiFi"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 当观察到变动时执行的回调函数
|
||
|
const popoverCallback = function (mutationsList, observer) {
|
||
|
for (let mutation of mutationsList) {
|
||
|
if (mutation.addedNodes.length === 1 && mutation.addedNodes[0].id === 'context-menu-popover') {
|
||
|
// 漫画右击菜单增加下载漫画选项
|
||
|
let context_menu = document.body.querySelector('#context-menu-popover')
|
||
|
let li = document.createElement('li')
|
||
|
li.className = '_3DwHO40e3Z-gvSYKDY3tKj _1cviuhBqCp4QyL5XTodCoi'
|
||
|
let a = document.createElement('a')
|
||
|
a.className = '_2_SVV5xt3XssHDHqrTGFt0'
|
||
|
a.innerText = '下载漫画'
|
||
|
a.id = "download-book"
|
||
|
|
||
|
a.onclick = () => {
|
||
|
let book = itemViewResponse.itemsList.find(s => s.asin === currentBook)
|
||
|
if (book === undefined) {
|
||
|
debugger
|
||
|
throw new Error("无法找到漫画信息")
|
||
|
}
|
||
|
|
||
|
//加载漫画页读取漫画元数据
|
||
|
fetch(book.webReaderUrl).then(res => res.text()).then(res => {
|
||
|
let domparser = new DOMParser()
|
||
|
let doc = domparser.parseFromString(res, 'text/html')
|
||
|
let txtBookManifest = JSON.parse(doc.querySelector('#txtBookManifest').innerText)
|
||
|
console.debug(txtBookManifest)
|
||
|
let bookInfo = JSON.parse(doc.querySelector('#bookInfo').innerText)
|
||
|
console.debug(bookInfo)
|
||
|
|
||
|
|
||
|
let scramblePromise = []
|
||
|
let scrambleList = Object.keys(txtBookManifest.manifest.resources).filter(item => item.startsWith('scramble_maps'))
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: `获取漫画图片裁剪数据` })
|
||
|
let successCount = 0
|
||
|
for (let image of scrambleList) {
|
||
|
// 获取漫画图片裁剪数据
|
||
|
|
||
|
|
||
|
scramblePromise.push(new Promise((resolve, reject) => {
|
||
|
let scrambleMap = txtBookManifest.manifest.resources[image]
|
||
|
console.debug(scrambleMap)
|
||
|
let scrambleUrl = JSON.parse(scrambleMap['url'])
|
||
|
console.debug(scrambleUrl);
|
||
|
setTimeout(() => {
|
||
|
fetch(scrambleUrl.cde, {
|
||
|
headers: {
|
||
|
...createHeader(),
|
||
|
"X-ADP-Request-Digest": scrambleUrl.requestDigest,
|
||
|
"X-ADP-Authentication-Token": bookInfo.adpDeviceToken
|
||
|
}
|
||
|
}).then(res => res.json()).then(res => {
|
||
|
successCount++
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: `加载漫画图片裁剪数据进度:${successCount} / ${scrambleList.length}` })
|
||
|
resolve(res)
|
||
|
}).catch(e => reject(e))
|
||
|
}, 500 * scramblePromise.length)
|
||
|
|
||
|
}))
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
Promise.all(scramblePromise).then(values => {
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: '图片裁剪数据加载成功' })
|
||
|
//合并漫画裁剪数据
|
||
|
let scrambleData = Object.assign(...values)
|
||
|
console.debug(scrambleData)
|
||
|
|
||
|
let imageBlobPromise = []
|
||
|
let imageList = Object.keys(txtBookManifest.manifest.resources).filter(item => item.startsWith('img'))
|
||
|
|
||
|
successCount = 0
|
||
|
chrome.runtime.sendMessage({ start: true })
|
||
|
for (let ref of imageList) {
|
||
|
imageBlobPromise.push(new Promise((resolve, reject) => {
|
||
|
let imageContent = txtBookManifest.manifest.resources[ref]
|
||
|
console.debug(imageContent)
|
||
|
let url = JSON.parse(imageContent['url'])
|
||
|
console.debug(url)
|
||
|
fetch(url.cde, {
|
||
|
headers: {
|
||
|
...createHeader(),
|
||
|
"X-ADP-Request-Digest": url.requestDigest,
|
||
|
"X-ADP-Authentication-Token": bookInfo.adpDeviceToken
|
||
|
}
|
||
|
}).then(res => res.blob()).then(res => {
|
||
|
let blobUrl = URL.createObjectURL(res)
|
||
|
var k = new Image()
|
||
|
// 还原图片
|
||
|
k.onload = () => {
|
||
|
URL.revokeObjectURL(blobUrl)
|
||
|
|
||
|
let b = scrambleData[ref]
|
||
|
for (var d = 0, e = 0, f = 0; f < b.length; f += 6) {
|
||
|
d = Math.max(d, b[f + 1] + b[f + 5])
|
||
|
e = Math.max(e, b[f] + b[f + 4])
|
||
|
}
|
||
|
let canvas = document.createElement("canvas")
|
||
|
canvas.height = d
|
||
|
canvas.width = e
|
||
|
let ctx = canvas.getContext("2d");
|
||
|
for (f = 0; f < b.length; f += 6) {
|
||
|
e = b[f + 4];
|
||
|
var g = b[f + 5];
|
||
|
ctx.drawImage(k, b[f + 2], b[f + 3], e, g, b[f], b[f + 1], e, g)
|
||
|
}
|
||
|
canvas.toBlob(blob => {
|
||
|
// let newBlobUrl = URL.createObjectURL(blob)
|
||
|
// console.debug(`ref url:\t${newBlobUrl}`)
|
||
|
successCount++
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: `下载漫画图片进度:${successCount}/${imageList.length}`, progress: successCount / imageList.length });
|
||
|
resolve({ name: ref, blob })
|
||
|
})
|
||
|
}
|
||
|
k.onerror = (event, source) => reject(event)
|
||
|
k.src = blobUrl
|
||
|
})
|
||
|
}))
|
||
|
}
|
||
|
|
||
|
// 打包图片并下载
|
||
|
Promise.all(imageBlobPromise).then(values => {
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: '打包漫画图片' });
|
||
|
var zip = new JSZip()
|
||
|
for (let blob of values) {
|
||
|
zip.file(blob.name, blob.blob);
|
||
|
}
|
||
|
zip.generateAsync({ type: "blob" })
|
||
|
.then(function (content) {
|
||
|
// see FileSaver.js
|
||
|
chrome.runtime.sendMessage({ title: book.title, status: '打包完成,下载弹窗中选择保存路径。' });
|
||
|
saveAs(content, `${book.title}.zip`);
|
||
|
chrome.runtime.sendMessage({ done: true })
|
||
|
setTimeout(() => {
|
||
|
chrome.runtime.sendMessage({ title: '', status: '' })
|
||
|
}, 3000)
|
||
|
});
|
||
|
}).catch(e => {
|
||
|
alert('图片打包失败,请稍后再试')
|
||
|
throw new Error(e)
|
||
|
})
|
||
|
}).catch(e => {
|
||
|
alert('图片裁剪数据加载失败,请稍后再试')
|
||
|
throw new Error(e)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
}
|
||
|
li.append(a)
|
||
|
context_menu.querySelector('ul').append(li)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 获取当前选中的漫画
|
||
|
const mangalistCallback = function (mutationsList, observer) {
|
||
|
for (let mutation of mutationsList) {
|
||
|
if (mutation.addedNodes.length === 1) {
|
||
|
mutation.addedNodes[0].querySelectorAll('ul>li').forEach((a, b) => {
|
||
|
a.oncontextmenu = (a) => {
|
||
|
currentBook = a.target.id.split('-')[1]
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function observe() {
|
||
|
// 创建一个观察器实例并传入回调函数
|
||
|
popoverObserver = new MutationObserver(popoverCallback);
|
||
|
// 以上述配置开始观察目标节点
|
||
|
popoverObserver.observe(popover, config);
|
||
|
|
||
|
mangalistObserver = new MutationObserver(mangalistCallback)
|
||
|
mangalistObserver.observe(recentlyPurchasedCarousel, config)
|
||
|
}
|
||
|
|
||
|
function disconnect() {
|
||
|
// 之后,可停止观察
|
||
|
popoverObserver.disconnect();
|
||
|
mangalistObserver.disconnect()
|
||
|
}
|
||
|
|
||
|
//打开的链接是漫画筛选页面,才添加下载漫画功能
|
||
|
|
||
|
if (location.href.includes('resourceType=COMICS')) {
|
||
|
observe()
|
||
|
} else if (popoverObserver) {
|
||
|
disconnect()
|
||
|
}
|
||
|
|
||
|
if (side_bar_filters) {
|
||
|
side_bar_filters.addEventListener('click', function (a) {
|
||
|
if (a.target.id === 'sidebar_group_SIDE_BAR_FILTERS_COMICS_button') {
|
||
|
observe()
|
||
|
} else {
|
||
|
disconnect()
|
||
|
}
|
||
|
})
|
||
|
}
|