您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
melonbooks阅读器图源自动下载
// ==UserScript== // @name melonbooks图源获取 // @namespace summer-script // @version 0.6 // @description melonbooks阅读器图源自动下载 // @author summer // @match https://www.melonbooks.co.jp/viewer/* // @match https://www.melonbooks.co.jp/fromagee/viewer/* // @icon https://www.melonbooks.co.jp/favicon.ico // @license GPL-3.0 // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_download // @connect cloudfront.net // ==/UserScript== (function () { 'use strict'; // 界面按钮 var ui = { btn: null, appendStartBtn: function () { var btn = document.createElement('button'); btn.innerText = '下载图源'; btn.style.position = 'fixed'; btn.style.top = '20px'; btn.style.right = '20px'; btn.style.zIndex = '10001'; btn.style.padding = '8px'; btn.style.background = '#fff'; btn.style.border = '1px solid #aaa'; btn.style.borderRadius = '4px'; btn.style.minWidth = '112px'; btn.style.color = '#000'; document.body.appendChild(btn); this.btn = btn; }, btnClick: function (callback) { var btn = this.btn; btn.onclick = function () { callback(btn); }; }, updateBtnText: function (text) { this.btn.innerText = text; } }; // 加载图像, 获取书籍相关参数 var loader = { checkValidity: function () { if (!unsafeWindow.GUARDIAN_SERVER) { return false; } if (!unsafeWindow.book_data) { return false; } if (!unsafeWindow.pages_data) { return false; } if (!unsafeWindow.pages_data.keys) { return false; } return true; }, checkVersion: function () { var verSupport = [0, 3, 4, 5]; var bookData = pageUtil.getBookData(); var verCurrent = bookData.version; if (!verCurrent) { verCurrent = 0; } return verSupport.includes(verCurrent); }, getPageKey: function (page) { return unsafeWindow.pages_data.keys[page - 1]; }, getMaxPage: function () { return pageUtil.getBookData().page_count; }, getBookName: function () { var bookName = pageUtil.getBookData().title; if (!bookName) { bookName = document.title; } return bookName; }, loadPage: function (page, callback) { var img = new Image(); var imgKey = this.getPageKey(page); if (callback) { img.onload = function () { callback(img, imgKey); }; } imgUrlUtil.getImageUrl(page).then(function (imgSrc) { // console.log(imgSrc); GM_xmlhttpRequest({ method: 'GET', url: imgSrc, responseType: 'blob', onload: function (resp) { fileDecryptor.decrypt(resp.response, imgKey).then(function (fileBlob) { img.src = URL.createObjectURL(fileBlob); }); } }); }); } }; // 绘制并下载正确的图像 var render = { canvas: null, ctx: null, drawImage: function (img, coorArr) { var canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; var ctx = canvas.getContext('2d'); this.drawOrigin(ctx, img, coorArr); URL.revokeObjectURL(img.src); this.canvas = canvas; this.ctx = ctx; }, downloadImage: function (filename) { if (!this.canvas) { return; } var fullname = filename + '.png'; this.canvas.toBlob(function (blob) { if (!blob) { console.log("blob null"); return; } var blobURL = URL.createObjectURL(blob); GM_download({ name: fullname, url: blobURL, onload: function () { URL.revokeObjectURL(blobURL); }, }); }); }, drawOrigin: function (ctx, img, coorArr) { var blockEachLine = Math.floor(img.naturalWidth / 96); ctx.drawImage(img, 0, 0); for (var i = 0; i < coorArr.length; i++) { var dstX = Math.floor(coorArr[i] % blockEachLine) * 96; var dstY = Math.floor(coorArr[i] / blockEachLine) * 128; var srcX = Math.floor(i % blockEachLine) * 96; var srcY = Math.floor(i / blockEachLine) * 128; ctx.drawImage(img, srcX, srcY, 96, 128, dstX, dstY, 96, 128); } } }; // 原网页复制过来的洗牌方法 // 每页对应一个key, key作为随机种子还原真实顺序 var randomizer = { PARAM_A: 1103515245, PARAM_B: 12345, RAND_MAX: 32767, init: function (e) { this.next = this._str_to_int(e); }, rand: function (t) { var n; return null != t ? (n = t + 1, Math.floor(this._next_int() / (Math.floor(this.RAND_MAX / n) + 1))) : this._next_int(); }, shuffle: function (e) { var t, n, i, r, o, s; o = [].concat(e); r = o.length; for (t = i = 0; 0 <= r ? i < r : i > r; t = 0 <= r ? ++i : --i) n = this.rand(o.length - 1), s = o[n], o[n] = o[t], o[t] = s; return o; }, _next_int: function () { this.next = (this.next * this.PARAM_A + this.PARAM_B) % (this.RAND_MAX + 1); return this.next; }, _str_to_int: function (e) { var t, n, i, r, o, s; if (s = 0, null != e) for (t = e.split(""); t.length > 0;) n = t.shift(), i = t.shift(), r = n.charCodeAt(0), o = 0, i && (o = i.charCodeAt(0)), s += r << 8 | o; return s; }, getImgCoorArr: function (img) { var o = Math.floor(img.naturalWidth / 96); var s = Math.floor(img.naturalHeight / 128); var r = []; for (var l = 0; l < o * s; ++l) { r.push(l); } return this.shuffle(r); } }; var imgUrlUtil = { getImageUrl: async function (page) { var bookData = pageUtil.getBookData(); var imgExt = this.getFileExt(); var urlPath = await this.getImageUrlPath(page); var url = unsafeWindow.GUARDIAN_SERVER + '/'; url += bookData.s3_key; url += urlPath + '.' + imgExt; return url; }, getFileExt: function () { var bookData = pageUtil.getBookData(); var imgExt = 'jpg'; if (bookData.image_extension) { imgExt = bookData.image_extension; } return imgExt; }, getImageUrlPath: async function (page) { var bookData = pageUtil.getBookData(); var path = page; if (bookData.page_salt) { path = await this.fileHashName(bookData.page_salt, page); } return path; }, fileHashName: async function (e, n) { // e => salt // n => page index const t = new TextEncoder; n = t.encode(n), e = t.encode(e), e = await crypto.subtle.importKey("raw", e, { name: "HMAC", hash: { name: "SHA-256" } }, !1, ["sign"]), e = await crypto.subtle.sign("HMAC", e, n); { n = e; let t = ""; var i = new Uint8Array(n) , o = i.byteLength; for (let e = 0; e < o; e++) t += String.fromCharCode(i[e]); return btoa(t).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") } } }; var pageUtil = { bookData: null, getScript: function () { var script = null; document.body.querySelectorAll("script").forEach(function (dom) { if (!script && dom.innerHTML.includes('GUARDIAN_SERVER')) { script = dom.innerHTML; } }); return script; }, getBookData: function () { if (this.bookData) { return this.bookData; } var script = this.getScript(); var reg = /book_data\s*=\s*(\{.+?\});/; var match = reg.exec(script); if (!match) { return unsafeWindow.book_data; } var bookData = JSON.parse(match[1]); if ( bookData.s3_key && bookData.s3_key[bookData.s3_key.length - 1] !== "/" ) { bookData.s3_key += "/"; } this.bookData = bookData; return this.bookData; } }; var fileDecryptor = { aesKey: null, decrypt: async function (fileBlob, pageKey) { var bookData = pageUtil.getBookData(); if (this.isEncrypted()) { var aesKeyEncrypt = bookData.crypto.data.web; fileBlob = await this.aesDecrypt(fileBlob, aesKeyEncrypt, pageKey); } return fileBlob; }, aesDecrypt: async function (fileBlob, aesKeyEncrypt, pageKey) { var pageKeyArr = (new TextEncoder()).encode(pageKey); var aesKey = await this.decryptAesKey(aesKeyEncrypt); var aesKeyHash = await this.hmacSha256(aesKey, pageKeyArr); var aesKeyC = await window.crypto.subtle.importKey("raw", aesKeyHash, { name: "AES-CTR", length: 256 }, false, ["decrypt"]); var counter = await fileBlob.slice(16, 32).arrayBuffer(); var fileDataBlob = await fileBlob.slice(32).arrayBuffer(); var fileDataDecrypt = await window.crypto.subtle.decrypt({ name: "AES-CTR", counter: counter, length: 128 }, aesKeyC, fileDataBlob); return new Blob([fileDataDecrypt], { type: this.getFileMimeType() }); }, decryptAesKey: async function (cipherText64) { if (this.aesKey) { return this.aesKey; } var rsaKey = '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' + 'Qi5Wa3DymTfNsvcXVG+/n3OOEIoQ=='; var pk = await window.crypto.subtle.importKey( "pkcs8", this.base64ToUint8(rsaKey), { name: "RSA-OAEP", hash: { name: "SHA-256" } }, false, ["decrypt"] ); this.aesKey = await window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, pk, this.base64ToUint8(cipherText64) ); return this.aesKey; }, base64ToUint8: function (n) { const t = window.atob(n); var r = t.length; const e = new Uint8Array(r); for (let n = 0; n < r; n++) { e[n] = t.charCodeAt(n); } return e.buffer; }, hmacSha256: async function (key, data) { var keyC = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: { name: "SHA-256" } }, false, ["sign"]); return crypto.subtle.sign("HMAC", keyC, data); }, getFileMimeType: function () { var mimeType = 'image/jpeg'; var bookData = pageUtil.getBookData(); if (bookData.image_mime_type) { mimeType = bookData.image_mime_type } return mimeType; }, isEncrypted: function () { var bookData = pageUtil.getBookData(); return bookData.crypto && bookData.crypto.type === 1; } }; var downloader = { downloadImg: function (img, imgKey, fileName) { if (fileDecryptor.isEncrypted()) { var blobURL = img.src; var fullname = fileName + '.' + this.getFileExtByMimeType(); // v5版本文件加密但是不打乱 GM_download({ name: fullname, url: blobURL, onload: function () { URL.revokeObjectURL(blobURL); }, }); } else { randomizer.init(imgKey); var imgCoor = randomizer.getImgCoorArr(img); render.drawImage(img, imgCoor); render.downloadImage(fileName); } }, getFileExtByMimeType: function () { var bookData = pageUtil.getBookData(); var ext = 'png'; var map = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', }; var mimeType = bookData.image_mime_type; if (mimeType && map[mimeType]) { ext = map[mimeType] } return ext; } }; ui.appendStartBtn(); if (!loader.checkVersion()) { ui.updateBtnText('暂不支持此作品, 点击尝试运行'); } ui.btnClick(function (btn) { btn.disabled = true; if (!loader.checkValidity()) { alert('脚本已失效'); return; } taskRun(); }); // 顺序处理 function taskRun(pageNow) { if (!pageNow) { pageNow = 1; } var pageMax = loader.getMaxPage(); var fileNo = pageNow.toString().padStart(pageMax.toString().length, '0'); var fileName = loader.getBookName() + '_' + fileNo; if (pageNow > pageMax) { ui.updateBtnText('下载完毕'); return; } ui.updateBtnText('下载中: ' + pageNow + '/' + pageMax); loader.loadPage(pageNow, function (img, imgKey) { downloader.downloadImg(img, imgKey, fileName); pageNow++; // 防止请求过快延迟1秒 setTimeout(function () { taskRun(pageNow); }, 1000); }); } })();