Download chapters from Web-Ace
// ==UserScript==
// @name Web-Ace Chapter Downloader
// @name:ja Web-Ace チャプターダウンローダー
// @namespace https://ceavan.com/
// @version 1.3
// @description Download chapters from Web-Ace
// @description:ja Web-Aceから章をダウンロード
// @author ceavan
// @icon https://appfav.net/image/thum_icon1/20200311105141474179OW21flGbBw.jpg
// @license MIT
// @match https://web-ace.jp/*/episode/*/
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==
(function() {
'use strict';
// Load settings
function loadSettings() {
return {
useCBZ: GM_getValue("useCBZ", false),
includeChapterTitle: GM_getValue("includeChapterTitle", true),
};
}
let { useCBZ, includeChapterTitle } = loadSettings();
let menuIDs = [];
// Function to save settings and refresh menu
function updateSetting(key, value) {
GM_setValue(key, value);
({ useCBZ, includeChapterTitle } = loadSettings());
registerMenuCommands();
}
// Function to create and show a progress bar
function createProgressBar() {
let progressContainer = document.createElement("div");
progressContainer.id = "progress-container";
progressContainer.style.position = "fixed";
progressContainer.style.bottom = "80px";
progressContainer.style.right = "10px";
progressContainer.style.width = "300px";
progressContainer.style.height = "40px";
progressContainer.style.border = "1px solid #000";
progressContainer.style.background = "#ddd";
progressContainer.style.zIndex = 1000;
progressContainer.style.display = "flex";
progressContainer.style.alignItems = "center";
progressContainer.style.justifyContent = "center";
progressContainer.style.fontSize = "20px";
progressContainer.style.fontWeight = "bold";
progressContainer.style.color = "#000";
progressContainer.style.overflow = "hidden";
progressContainer.style.textAlign = "center";
let progressBar = document.createElement("div");
progressBar.id = "progress-bar";
progressBar.style.height = "100%";
progressBar.style.width = "0%";
progressBar.style.background = "#4caf50";
progressBar.style.position = "absolute";
progressBar.style.top = "0";
progressBar.style.left = "0";
progressBar.style.transition = "width 0.3s ease";
let progressText = document.createElement("div");
progressText.id = "progress-text";
progressText.style.position = "absolute";
progressText.style.width = "100%";
progressText.style.textAlign = "center";
progressText.style.fontSize = "20px";
progressText.style.fontWeight = "bold";
progressText.style.zIndex = "10";
progressText.style.whiteSpace = "pre-line";
progressContainer.appendChild(progressBar);
progressContainer.appendChild(progressText);
document.body.appendChild(progressContainer);
}
// Function to update the progress bar
function updateProgress(current, total) {
let progressBar = document.getElementById("progress-bar");
let progressText = document.getElementById("progress-text");
if (progressBar && progressText) {
let percent = Math.round((current / total) * 100);
progressBar.style.width = percent + "%";
progressText.innerText = `${current} / ${total}`;
}
}
// Function to update the final progress text when ZIP is being saved
function updateSavingText(total) {
let progressText = document.getElementById("progress-text");
if (progressText) {
progressText.innerText = `Saving ZIP...`;
}
}
// Function to update the final status after ZIP is saved
function updateCompletedText() {
let progressText = document.getElementById("progress-text");
let downloadButton = document.getElementById("download-chapter-button");
if (progressText) {
progressText.innerText = "Chapter downloaded";
}
if (downloadButton) {
setTimeout(() => {
downloadButton.innerText = "Chapter Downloaded";
}, 2000); // Reset button after 2 seconds
}
setTimeout(() => {
let progressContainer = document.getElementById("progress-container");
if (progressContainer) progressContainer.remove();
}, 2000); // Remove after 2 seconds
}
// Function to extract the chapter title from <p class="watitle">
function getChapterTitle() {
let titleElement = document.querySelector("p.watitle");
return titleElement ? titleElement.innerText.trim() : "";
}
// Function to download images and pack them into a zip file
function downloadImages(imageUrls) {
const zip = new JSZip();
let totalImages = imageUrls.length;
let padLength = totalImages.toString().length;
let downloadCount = 0;
createProgressBar();
imageUrls.forEach((url, index) => {
let fileNumber = String(index + 1).padStart(padLength, '0');
GM_xmlhttpRequest({
method: "GET",
url: url,
responseType: "blob",
onload: function(response) {
zip.file(`${fileNumber}.jpg`, response.response, { binary: true });
downloadCount++;
updateProgress(downloadCount, totalImages);
if (downloadCount === totalImages) {
updateSavingText(totalImages);
let title = document.title.split('|')[0].trim();
let chapterTitle = includeChapterTitle ? getChapterTitle() : "";
// Append chapter title if it exists
let finalFileName = chapterTitle ? `${title} - ${chapterTitle}` : title;
// Determine the extension based on CBZ or ZIP preference
let fileExtension = useCBZ ? "cbz" : "zip";
finalFileName = `${finalFileName}.${fileExtension}`;
zip.generateAsync({ type: "blob" }).then(function(content) {
saveAs(content, finalFileName);
updateCompletedText();
});
}
},
onerror: function(error) {
console.error('Failed to download image:', error);
}
});
});
}
// Function to get image URLs from JSON endpoint
function getImageUrls(jsonUrl) {
GM_xmlhttpRequest({
method: "GET",
url: jsonUrl,
onload: function(response) {
const imageUrls = JSON.parse(response.responseText);
downloadImages(imageUrls);
},
onerror: function(error) {
console.error('Failed to fetch image URLs:', error);
}
});
}
// Add download button to the page
const button = document.createElement("button");
button.id = "download-chapter-button";
button.innerHTML = "Download Chapter";
button.style.position = "fixed";
button.style.bottom = "40px";
button.style.right = "50px";
button.style.fontSize= "20px";
button.style.zIndex = 1000;
button.onclick = function() {
button.innerText = "Downloading chapter...";
button.disabled = true;
const chapterUrl = window.location.href;
const jsonUrl = chapterUrl + 'json/';
getImageUrls(jsonUrl);
};
document.body.appendChild(button);
// Function to register or refresh menu commands
function registerMenuCommands() {
// Unregister old menu commands
menuIDs.forEach(id => GM_unregisterMenuCommand(id));
menuIDs = []; // Reset stored command IDs
// Register new menu commands
menuIDs.push(GM_registerMenuCommand(
`${useCBZ ? "✅" : "❌"} Save as CBZ`,
() => {
updateSetting("useCBZ", !useCBZ);
}
));
menuIDs.push(GM_registerMenuCommand(
`${includeChapterTitle ? "✅" : "❌"} Include chapter title in filename`,
() => {
updateSetting("includeChapterTitle", !includeChapterTitle);
}
));
}
// Register menu commands on script load
registerMenuCommands();
})();