Helps download mangas from akuma.moe with ComicInfo.xml
// ==UserScript==
// @name akuma.Moe Downloader
// @namespace http://tampermonkey.net/
// @version 0.1a
// @description Helps download mangas from akuma.moe with ComicInfo.xml
// @author Nuark & temp.user
// @connect s1.akuma.moe
// @connect s2.akuma.moe
// @match https://*akuma.moe/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// @grant GM_xmlhttpRequest
// @grant GM_download
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let w = unsafeWindow;
let jq = w.$;
let blobholder = {
init: function(filename, details, maxcount, caller) {
if (!filename.endsWith('.zip')) {
filename += '.zip';
}
this.filename = filename;
this.details = details;
this.maxcount = maxcount;
this.caller = caller;
},
blobarray: [],
currcount: 0,
addBlob: function(blob) {
this.blobarray.push(blob);
this.currcount++;
console.log("blob insertion dispatched", blob, this.currcount, this.maxcount);
if (this.currcount === this.maxcount) {
this.callback();
this.caller.text("Serializing...");
console.log("calling serialization", blob, this.currcount, this.maxcount);
}
},
callback: function() {
let zip = new JSZip();
let comicInfoContent = createComicInfo();
zip.file("Comicinfo.xml", comicInfoContent);
let img = zip.folder("images");
this.blobarray.forEach(file => {
img.file(file[0], file[1]);
});
zip.generateAsync({type:"blob"}, function updateCallback(metadata) {
blobholder.caller.text(metadata.percent.toFixed(2) + '%');
})
.then(function(content) {
saveAs(content, blobholder.filename);
blobholder.caller.toggleClass("btn-info").toggleClass("btn-success");
blobholder.caller.text("Done");
});
}
}
w.zxc = async function (caller) {
caller = jq(caller);
let todo = w.pag.cnt;
let tpl = "{origin}{pathname}/{page}".replace(/(\{origin\})/, location.origin).replace(/(\{pathname\})/, location.pathname);
blobholder.init(jq(".entry-title").text(), "", todo, caller);
caller.text(`Pages: ${todo}`);
for (let i = 1; i <= todo; i++) {
caller.text(`Fetching ${i} of ${todo}...`);
let page_response = await fetch(tpl.replace(/(\{page\})/, i));
let html = await page_response.text();
let div = jq("<div>").html(html.replace(/(script)/g, "scr").replace(/(link)/g, "lnk"))[0];
let link = div.querySelector("#image-container > img").src;
GM_xmlhttpRequest({
method: "GET",
url: link,
synchronous: true,
responseType: "blob",
onload: function (response) {
let title = response.finalUrl .split("/").pop();;
let blob = response.response;
blobholder.addBlob([title, blob]);
}
});
}
}
function init() { //TODO: location.pathname detection
let new_btn = jq("#start").clone();
new_btn.id = "download";
new_btn.text("Download");
new_btn.attr("href", "#download");
new_btn.attr("title", "Download");
new_btn.toggleClass("btn-primary").toggleClass("btn-info");
new_btn.attr("onclick", "window.zxc(this)");
jq("#start").parent().append(new_btn);
}
function createComicInfo() {
const title = document.querySelector('h1.entry-title')?.textContent.trim();
const categories = Array.from(document.querySelectorAll('li.meta-data .value a[href*="category"]')).map(el => el.textContent.trim());
const languages = Array.from(document.querySelectorAll('li.meta-data .value a[href*="language"]')).map(el => el.textContent.trim());
const artists = Array.from(document.querySelectorAll('li.meta-data .value a[href*="artist"], li.meta-data .value a[href*="group"]')).map(el => el.textContent.trim());
const maleTags = Array.from(document.querySelectorAll('li.meta-data .value a[href*="male"]')).map(el => `male:${el.textContent.trim()}`);
const femaleTags = Array.from(document.querySelectorAll('li.meta-data .value a[href*="female"]')).map(el => `female:${el.textContent.trim()}`);
const mixedTags = Array.from(document.querySelectorAll('li.meta-data .value a[href*="mixed"]')).map(el => `mixed:${el.textContent.trim()}`);
const otherTags = Array.from(document.querySelectorAll('li.meta-data .value a[href*="other"]')).map(el => `other:${el.textContent.trim()}`);
const tags = [...maleTags, ...femaleTags, ...mixedTags, ...otherTags];
const date = document.querySelector('li.meta-data.date .value time')?.getAttribute('datetime').split(' ')[0]; // YYYY-MM-DD
const pages = document.querySelector('li.meta-data.pages .value')?.textContent.trim();
const url = window.location.href;
const comicInfo = `<?xml version="1.0"?>
<ComicInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Title>${title}</Title>
<Format>${categories.join(', ')}</Format>
<Language>${languages.join(', ')}</Language>
<Writer>${artists.join(', ')}</Writer>
<Tags>${tags.join(', ')}</Tags>
<Date>${date}</Date>
<Pages>${pages}</Pages>
<Web>${url}</Web>
</Comicinfo>`;
return comicInfo;
}
init();
})();