您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download Images From CoroCoro
// ==UserScript== // @name CoroCoroRipper // @namespace adrian // @author adrian // @match https://www.corocoro.jp/* // @version 1.0 // @description Download Images From CoroCoro // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1 // @require https://unpkg.com/@zip.js/[email protected]/dist/zip-full.min.js // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== const SHIFT_LEFT_32 = (1 << 16) * (1 << 16); const SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; // Threshold chosen based on both benchmarking and knowledge about browser string // data structures (which currently switch structure types at 12 bytes or more) const TEXT_DECODER_MIN_LENGTH = 12; const utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf-8'); const PBF_VARINT = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum const PBF_FIXED64 = 1; // 64-bit: double, fixed64, sfixed64 const PBF_BYTES = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields const PBF_FIXED32 = 5; // 32-bit: float, fixed32, sfixed32 class Pbf { /** * @param {Uint8Array | ArrayBuffer} [buf] */ constructor(buf = new Uint8Array(16)) { this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf); this.dataView = new DataView(this.buf.buffer); this.pos = 0; this.type = 0; this.length = this.buf.length; } // === READING ================================================================= /** * @template T * @param {(tag: number, result: T, pbf: Pbf) => void} readField * @param {T} result * @param {number} [end] */ readFields(readField, result, end = this.length) { while (this.pos < end) { const val = this.readVarint(), tag = val >> 3, startPos = this.pos; this.type = val & 0x7; readField(tag, result, this); if (this.pos === startPos) this.skip(val); } return result; } /** * @template T * @param {(tag: number, result: T, pbf: Pbf) => void} readField * @param {T} result */ readMessage(readField, result) { return this.readFields(readField, result, this.readVarint() + this.pos); } readFixed32() { const val = this.dataView.getUint32(this.pos, true); this.pos += 4; return val; } readSFixed32() { const val = this.dataView.getInt32(this.pos, true); this.pos += 4; return val; } // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed) readFixed64() { const val = this.dataView.getUint32(this.pos, true) + this.dataView.getUint32(this.pos + 4, true) * SHIFT_LEFT_32; this.pos += 8; return val; } readSFixed64() { const val = this.dataView.getUint32(this.pos, true) + this.dataView.getInt32(this.pos + 4, true) * SHIFT_LEFT_32; this.pos += 8; return val; } readFloat() { const val = this.dataView.getFloat32(this.pos, true); this.pos += 4; return val; } readDouble() { const val = this.dataView.getFloat64(this.pos, true); this.pos += 8; return val; } /** * @param {boolean} [isSigned] */ readVarint(isSigned) { const buf = this.buf; let val, b; b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val; b = buf[this.pos]; val |= (b & 0x0f) << 28; return readVarintRemainder(val, isSigned, this); } readVarint64() { // for compatibility with v2.0.1 return this.readVarint(true); } readSVarint() { const num = this.readVarint(); return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding } readBoolean() { return Boolean(this.readVarint()); } readString() { const end = this.readVarint() + this.pos; const pos = this.pos; this.pos = end; if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) { // longer strings are fast with the built-in browser TextDecoder API return utf8TextDecoder.decode(this.buf.subarray(pos, end)); } // short strings are fast with our custom implementation return readUtf8(this.buf, pos, end); } readBytes() { const end = this.readVarint() + this.pos, buffer = this.buf.subarray(this.pos, end); this.pos = end; return buffer; } // verbose for performance reasons; doesn't affect gzipped size /** * @param {number[]} [arr] * @param {boolean} [isSigned] */ readPackedVarint(arr = [], isSigned) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readVarint(isSigned)); return arr; } /** @param {number[]} [arr] */ readPackedSVarint(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readSVarint()); return arr; } /** @param {boolean[]} [arr] */ readPackedBoolean(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readBoolean()); return arr; } /** @param {number[]} [arr] */ readPackedFloat(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readFloat()); return arr; } /** @param {number[]} [arr] */ readPackedDouble(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readDouble()); return arr; } /** @param {number[]} [arr] */ readPackedFixed32(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readFixed32()); return arr; } /** @param {number[]} [arr] */ readPackedSFixed32(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readSFixed32()); return arr; } /** @param {number[]} [arr] */ readPackedFixed64(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readFixed64()); return arr; } /** @param {number[]} [arr] */ readPackedSFixed64(arr = []) { const end = this.readPackedEnd(); while (this.pos < end) arr.push(this.readSFixed64()); return arr; } readPackedEnd() { return this.type === PBF_BYTES ? this.readVarint() + this.pos : this.pos + 1; } /** @param {number} val */ skip(val) { const type = val & 0x7; if (type === PBF_VARINT) while (this.buf[this.pos++] > 0x7f) { } else if (type === PBF_BYTES) this.pos = this.readVarint() + this.pos; else if (type === PBF_FIXED32) this.pos += 4; else if (type === PBF_FIXED64) this.pos += 8; else throw new Error(`Unimplemented type: ${type}`); } // === WRITING ================================================================= /** * @param {number} tag * @param {number} type */ writeTag(tag, type) { this.writeVarint((tag << 3) | type); } /** @param {number} min */ realloc(min) { let length = this.length || 16; while (length < this.pos + min) length *= 2; if (length !== this.length) { const buf = new Uint8Array(length); buf.set(this.buf); this.buf = buf; this.dataView = new DataView(buf.buffer); this.length = length; } } finish() { this.length = this.pos; this.pos = 0; return this.buf.subarray(0, this.length); } /** @param {number} val */ writeFixed32(val) { this.realloc(4); this.dataView.setInt32(this.pos, val, true); this.pos += 4; } /** @param {number} val */ writeSFixed32(val) { this.realloc(4); this.dataView.setInt32(this.pos, val, true); this.pos += 4; } /** @param {number} val */ writeFixed64(val) { this.realloc(8); this.dataView.setInt32(this.pos, val & -1, true); this.dataView.setInt32(this.pos + 4, Math.floor(val * SHIFT_RIGHT_32), true); this.pos += 8; } /** @param {number} val */ writeSFixed64(val) { this.realloc(8); this.dataView.setInt32(this.pos, val & -1, true); this.dataView.setInt32(this.pos + 4, Math.floor(val * SHIFT_RIGHT_32), true); this.pos += 8; } /** @param {number} val */ writeVarint(val) { val = +val || 0; if (val > 0xfffffff || val < 0) { writeBigVarint(val, this); return; } this.realloc(4); this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = (val >>> 7) & 0x7f; } /** @param {number} val */ writeSVarint(val) { this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); } /** @param {boolean} val */ writeBoolean(val) { this.writeVarint(+val); } /** @param {string} str */ writeString(str) { str = String(str); this.realloc(str.length * 4); this.pos++; // reserve 1 byte for short string length const startPos = this.pos; // write the string directly to the buffer and see how much was written this.pos = writeUtf8(this.buf, str, this.pos); const len = this.pos - startPos; if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position this.pos = startPos - 1; this.writeVarint(len); this.pos += len; } /** @param {number} val */ writeFloat(val) { this.realloc(4); this.dataView.setFloat32(this.pos, val, true); this.pos += 4; } /** @param {number} val */ writeDouble(val) { this.realloc(8); this.dataView.setFloat64(this.pos, val, true); this.pos += 8; } /** @param {Uint8Array} buffer */ writeBytes(buffer) { const len = buffer.length; this.writeVarint(len); this.realloc(len); for (let i = 0; i < len; i++) this.buf[this.pos++] = buffer[i]; } /** * @template T * @param {(obj: T, pbf: Pbf) => void} fn * @param {T} obj */ writeRawMessage(fn, obj) { this.pos++; // reserve 1 byte for short message length // write the message directly to the buffer and see how much was written const startPos = this.pos; fn(obj, this); const len = this.pos - startPos; if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position this.pos = startPos - 1; this.writeVarint(len); this.pos += len; } /** * @template T * @param {number} tag * @param {(obj: T, pbf: Pbf) => void} fn * @param {T} obj */ writeMessage(tag, fn, obj) { this.writeTag(tag, PBF_BYTES); this.writeRawMessage(fn, obj); } /** * @param {number} tag * @param {number[]} arr */ writePackedVarint(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedSVarint(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr); } /** * @param {number} tag * @param {boolean[]} arr */ writePackedBoolean(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedFloat(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedDouble(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedFixed32(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedSFixed32(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedFixed64(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr); } /** * @param {number} tag * @param {number[]} arr */ writePackedSFixed64(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); } /** * @param {number} tag * @param {Uint8Array} buffer */ writeBytesField(tag, buffer) { this.writeTag(tag, PBF_BYTES); this.writeBytes(buffer); } /** * @param {number} tag * @param {number} val */ writeFixed32Field(tag, val) { this.writeTag(tag, PBF_FIXED32); this.writeFixed32(val); } /** * @param {number} tag * @param {number} val */ writeSFixed32Field(tag, val) { this.writeTag(tag, PBF_FIXED32); this.writeSFixed32(val); } /** * @param {number} tag * @param {number} val */ writeFixed64Field(tag, val) { this.writeTag(tag, PBF_FIXED64); this.writeFixed64(val); } /** * @param {number} tag * @param {number} val */ writeSFixed64Field(tag, val) { this.writeTag(tag, PBF_FIXED64); this.writeSFixed64(val); } /** * @param {number} tag * @param {number} val */ writeVarintField(tag, val) { this.writeTag(tag, PBF_VARINT); this.writeVarint(val); } /** * @param {number} tag * @param {number} val */ writeSVarintField(tag, val) { this.writeTag(tag, PBF_VARINT); this.writeSVarint(val); } /** * @param {number} tag * @param {string} str */ writeStringField(tag, str) { this.writeTag(tag, PBF_BYTES); this.writeString(str); } /** * @param {number} tag * @param {number} val */ writeFloatField(tag, val) { this.writeTag(tag, PBF_FIXED32); this.writeFloat(val); } /** * @param {number} tag * @param {number} val */ writeDoubleField(tag, val) { this.writeTag(tag, PBF_FIXED64); this.writeDouble(val); } /** * @param {number} tag * @param {boolean} val */ writeBooleanField(tag, val) { this.writeVarintField(tag, +val); } }; /** * @param {number} l * @param {boolean | undefined} s * @param {Pbf} p */ function readVarintRemainder(l, s, p) { const buf = p.buf; let h, b; b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s); throw new Error('Expected varint not more than 10 bytes'); } /** * @param {number} low * @param {number} high * @param {boolean} [isSigned] */ function toNum(low, high, isSigned) { return isSigned ? high * 0x100000000 + (low >>> 0) : ((high >>> 0) * 0x100000000) + (low >>> 0); } /** * @param {number} val * @param {Pbf} pbf */ function writeBigVarint(val, pbf) { let low, high; if (val >= 0) { low = (val % 0x100000000) | 0; high = (val / 0x100000000) | 0; } else { low = ~(-val % 0x100000000); high = ~(-val / 0x100000000); if (low ^ 0xffffffff) { low = (low + 1) | 0; } else { low = 0; high = (high + 1) | 0; } } if (val >= 0x10000000000000000 || val < -0x10000000000000000) { throw new Error('Given varint doesn\'t fit into 10 bytes'); } pbf.realloc(10); writeBigVarintLow(low, high, pbf); writeBigVarintHigh(high, pbf); } /** * @param {number} high * @param {number} low * @param {Pbf} pbf */ function writeBigVarintLow(low, high, pbf) { pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos] = low & 0x7f; } /** * @param {number} high * @param {Pbf} pbf */ function writeBigVarintHigh(high, pbf) { const lsb = (high & 0x07) << 4; pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f; } /** * @param {number} startPos * @param {number} len * @param {Pbf} pbf */ function makeRoomForExtraLength(startPos, len, pbf) { const extraLen = len <= 0x3fff ? 1 : len <= 0x1fffff ? 2 : len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7)); // if 1 byte isn't enough for encoding message length, shift the data to the right pbf.realloc(extraLen); for (let i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i]; } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedVarint(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedSVarint(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedFloat(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedDouble(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); } /** * @param {boolean[]} arr * @param {Pbf} pbf */ function writePackedBoolean(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedFixed32(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedSFixed32(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedFixed64(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); } /** * @param {number[]} arr * @param {Pbf} pbf */ function writePackedSFixed64(arr, pbf) { for (let i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); } // Buffer code below from https://github.com/feross/buffer, MIT-licensed /** * @param {Uint8Array} buf * @param {number} pos * @param {number} end */ function readUtf8(buf, pos, end) { let str = ''; let i = pos; while (i < end) { const b0 = buf[i]; let c = null; // codepoint let bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1; if (i + bytesPerSequence > end) break; let b1, b2, b3; if (bytesPerSequence === 1) { if (b0 < 0x80) { c = b0; } } else if (bytesPerSequence === 2) { b1 = buf[i + 1]; if ((b1 & 0xC0) === 0x80) { c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F); if (c <= 0x7F) { c = null; } } } else if (bytesPerSequence === 3) { b1 = buf[i + 1]; b2 = buf[i + 2]; if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) { c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F); if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) { c = null; } } } else if (bytesPerSequence === 4) { b1 = buf[i + 1]; b2 = buf[i + 2]; b3 = buf[i + 3]; if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F); if (c <= 0xFFFF || c >= 0x110000) { c = null; } } } if (c === null) { c = 0xFFFD; bytesPerSequence = 1; } else if (c > 0xFFFF) { c -= 0x10000; str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800); c = 0xDC00 | c & 0x3FF; } str += String.fromCharCode(c); i += bytesPerSequence; } return str; } /** * @param {Uint8Array} buf * @param {string} str * @param {number} pos */ function writeUtf8(buf, str, pos) { for (let i = 0, c, lead; i < str.length; i++) { c = str.charCodeAt(i); // code point if (c > 0xD7FF && c < 0xE000) { if (lead) { if (c < 0xDC00) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; lead = c; continue; } else { c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000; lead = null; } } else { if (c > 0xDBFF || (i + 1 === str.length)) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; } else { lead = c; } continue; } } else if (lead) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; lead = null; } if (c < 0x80) { buf[pos++] = c; } else { if (c < 0x800) { buf[pos++] = c >> 0x6 | 0xC0; } else { if (c < 0x10000) { buf[pos++] = c >> 0xC | 0xE0; } else { buf[pos++] = c >> 0x12 | 0xF0; buf[pos++] = c >> 0xC & 0x3F | 0x80; } buf[pos++] = c >> 0x6 & 0x3F | 0x80; } buf[pos++] = c & 0x3F | 0x80; } } return pos; } const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); async function createDecryptFunc(keyHex, ivHex) { const key = await window.crypto.subtle.importKey("raw", fromHexString(keyHex), "AES-CBC", true, [ "decrypt", ]); const iv = fromHexString(ivHex); return async (data) => { return await window.crypto.subtle.decrypt({ name: "AES-CBC", iv }, key, data); }; } function toPng(webp) { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const image = new Image(); image.src = URL.createObjectURL(new Blob([webp])); image.crossOrigin = "anonymous"; image.onload = (e) => { canvas.width = image.width; canvas.height = image.height; URL.revokeObjectURL(e.target.src); context.drawImage(e.target, 0, 0, canvas.width, canvas.height); canvas.toBlob( (data) => { resolve(data); }, "image/png", 100, ); }; image.onerror = (e) => reject(e); }); } function readRoot(tag, data, pbf) { if (tag === 2) { data.images = pbf.readMessage(readImageMessage, data.images); return; } if (tag === 19) { data.key = pbf.readString(); return; } if (tag === 20) { data.iv = pbf.readString(); return; } } function readImageMessage(tag, message, pbf) { if (tag === 1) { message.push(pbf.readString()); return; } } const decodeProtobuf = (data) => new Pbf(data).readFields(readRoot, { images: [] }); const downloadImages = async () => { if ( !/https:\/\/www\.corocoro\.jp\/chapter\/.*\/viewer/.test( window.location.href, ) ) return; const progressBar = document.createElement("div"); progressBar.id = "dl-progress"; progressBar.textContent = "Starting..."; progressBar.style.padding = "20px"; progressBar.style.backgroundColor = "black"; progressBar.style.borderRadius = "10px"; progressBar.style.border = "1px solid white"; progressBar.style.boxShadow = "0 25px 50px -12px rgb(0 0 0 / 0.25)"; progressBar.style.position = "fixed"; progressBar.style.left = "50%"; progressBar.style.top = "50%"; progressBar.style.transform = "translate(-50%,-50%)"; progressBar.style.zIndex = "9999"; progressBar.style.fontSize = "20px"; progressBar.style.color = "white"; document.body.appendChild(progressBar); const currentPath = window.location.pathname; const pathSplit = currentPath.split("/"); pathSplit.pop(); const chapterId = pathSplit.pop(); const apiData = decodeProtobuf(await fetch( `https://www.corocoro.jp/api/csr?rq=chapter/viewer&chapter_id=${chapterId}`, { method: "PUT", }, ).then((res) => res.arrayBuffer())); const images = apiData.images; console.log(images); progressBar.textContent = `${images.length} images found.`; const zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), { bufferedWrite: true, }); const decrypt = await createDecryptFunc(apiData.key, apiData.iv); for (let i = 0; i < images.length; i++) { const image = images[i]; const response = await fetch(image); if (!response.ok) { progressBar.textContent = `failed to fetch image ${i + 1}/${images.length}`; throw new Error("Failed to fetch image"); } const arrayBuffer = await response.arrayBuffer(); const decryptedData = await decrypt(new Uint8Array(arrayBuffer)); zipWriter.add( `${i + 1}.png`, new zip.BlobReader(await toPng(decryptedData)), {}, ); progressBar.textContent = `fetched and decrypted image ${i + 1}/${images.length}`; console.log("done with ", i + 1); } console.log("image fetching done. generating zip"); progressBar.textContent = "image fetching done. generating zip"; const blobURL = URL.createObjectURL(await zipWriter.close()); const link = document.createElement("a"); link.href = blobURL; link.download = `${document.title}.zip`; link.click(); progressBar.textContent = "done."; setTimeout(() => progressBar.remove(), 1000); }; const updateButton = () => { console.log("loading"); let dlButton = document.body.querySelector("#dl-button"); if (!dlButton) { dlButton = document.createElement("button"); dlButton.id = "dl-button"; dlButton.textContent = "Download"; dlButton.style.padding = "5px 12px"; dlButton.style.backgroundColor = "#ef0029"; dlButton.style.borderRadius = "8px"; dlButton.style.border = "3px solid #000"; dlButton.style.boxShadow = "0 4px 0 #000"; dlButton.style.position = "absolute"; dlButton.style.right = "5px"; dlButton.style.bottom = "5px"; dlButton.style.zIndex = "9999"; dlButton.style.fontSize = ".75rem"; dlButton.style.fontWeight = "800"; dlButton.style.color = "white"; dlButton.addEventListener("click", () => downloadImages()); document.body.appendChild(dlButton); } dlButton.style.display = /https:\/\/www\.corocoro\.jp\/chapter\/.*\/viewer/.test( window.location.href, ) ? "block" : "none"; }; updateButton(); (() => { let oldPushState = history.pushState; history.pushState = function pushState() { let ret = oldPushState.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); window.dispatchEvent(new Event('locationchange')); return ret; }; let oldReplaceState = history.replaceState; history.replaceState = function replaceState() { let ret = oldReplaceState.apply(this, arguments); window.dispatchEvent(new Event('replacestate')); window.dispatchEvent(new Event('locationchange')); return ret; }; window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); })(); window.addEventListener('locationchange', function () { updateButton(); }); VM.shortcut.register("cm-s", () => downloadImages()); VM.shortcut.enable(); GM_registerMenuCommand("Download Images (Ctrl/Cmd + S)", () => downloadImages(), );