Download Images From Comic-Walker
目前為
// ==UserScript== // @name ComicWalkerRipper // @namespace adrian // @author adrian // @match https://comic-walker.com/detail/*/episodes/* // @version 1.0 // @description Download Images From Comic-Walker // @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 createKey = (hash) => { const parts = hash.slice(0, 16).match(/[\da-f]{2}/gi); return new Uint8Array(parts.map((data) => Number.parseInt(data, 16))); }; function createXorFunc(key) { return (image) => { const { length: imageLength } = image; const { length: keyLength } = key; const decrypted = new Uint8Array(imageLength); for (let index = 0; index < imageLength; index += 1) decrypted[index] = image[index] ^ key[index % keyLength]; return decrypted; }; } const downloadImages = async () => { 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 currentUrl = window.location.href; const parser = new DOMParser(); const fetchedHTML = await fetch(currentUrl).then((res) => res.text()); const doc = parser.parseFromString(fetchedHTML, "text/html"); const nextData = JSON.parse(doc.getElementById("__NEXT_DATA__").text); let episodeId; for (const query of nextData.props.pageProps.dehydratedState.queries) { if (query.queryKey[0] === "/api/contents/details/episode") { episodeId = query.state.data.episode.id; } } if (!episodeId) { progressBar.textContent = "unable to find episodeId."; setTimeout(() => progressBar.remove(), 1000); } console.log(episodeId); const apiData = await fetch( `https://comic-walker.com/api/contents/viewer?episodeId=${episodeId}&imageSizeType=width:1284`, ).then((res) => res.json()); const images = apiData.manuscripts; console.log(images); progressBar.textContent = `${images.length} images found.`; const zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/zip"), { bufferedWrite: true, }); for (let i = 0; i < images.length; i++) { const image = images[i]; const xorKey = createKey(image.drmHash); const xorFunc = createXorFunc(xorKey); const response = await fetch(image.drmImageUrl); 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 = xorFunc(new Uint8Array(arrayBuffer)); zipWriter.add(`${i + 1}.webp`, new zip.Uint8ArrayReader(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); }; VM.shortcut.register("cm-s", downloadImages); VM.shortcut.enable(); GM_registerMenuCommand("Download Images (Ctrl/Cmd + S)", downloadImages);