// ==UserScript==
// @name Mangadex List Exporter
// @namespace https://github.com/MarvNC
// @version 0.22
// @description A userscript for exporting a MangaDex list to a .xml file for import to anime list sites.
// @author Marv
// @match https://mangadex.org/list*
// @icon https://mangadex.org/favicon.ico
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @grant none
// @downloadURL none
// ==/UserScript==
// 1000ms delay between requests for MangaDex
const DELAY = 1000;
(function () {
'use strict';
// main function that executes on button click
let save = async () => {
// disable the button
btn.onclick = null;
let userID = /(?<=\/list\/)\d+/.exec(document.URL)[0];
let url = `https://mangadex.org/api/v2/user/${userID}/followed-manga`;
let response = await $.get(url);
let IDs = response.data;
console.log(IDs);
// prettier-ignore
let xml =
`
2
`;
// create timer counting down to remaining time
let countdownTimer = document.createElement('p');
countdownTimer.style = 'text-align:center;';
btn.parentElement.appendChild(countdownTimer);
// loop through each manga ID in IDs
for (let i = 0; i < IDs.length; i++) {
console.log(`${i + 1} of ${IDs.length}: Getting details for manga ID: ${IDs[i].mangaId}`);
// update time remaining, accounting for different delays
countdownTimer.innerHTML = `Export time remaining: ${formatSeconds(
((IDs.length - i - 1) * DELAY) / 1000
)}`;
// get the info from the manga then add it to xml
getMangaInfo(IDs[i]).then((mangaInfo) => {
btn.innerHTML = `${i + 1} of ${IDs.length} entries: Retrieved data for ${
mangaInfo.mangaTitle
}`;
// prettier-ignore
xml +=
`
${mangaInfo.malID}
${mangaInfo.mdID}
${mangaInfo.alID}
${mangaInfo.kitsuID}
${mangaInfo.muID}
${mangaInfo.volume}
${mangaInfo.chapter}
0000-00-00
0000-00-00
${mangaInfo.rating}
${mangaInfo.status}
0
`;
});
await timer(DELAY);
}
xml += ``;
btn.innerHTML = `Completed list export of ${IDs.length} entries!`;
// save the xml string as an xml with current date as filename
let date = new Date();
let filename = `mangalist_${date.toISOString()}.xml`;
let blob = new Blob([xml], {
type: 'application/xml',
});
saveAs(blob, filename);
};
// the button to add
var btn = document.createElement('BUTTON');
btn.innerHTML = `Click to export list; remember to set view mode to 'Simple list'`;
btn.onclick = save;
// add the button after user banner
document.getElementsByClassName('card mb-3')[0].append(btn);
})();
// accepts a manga list object thing
var getMangaInfo = async (manga) => {
const statuses = {
1: 'Reading',
2: 'Completed',
3: 'On hold',
4: 'Plan to read',
5: 'Dropped',
6: 'Re-reading',
};
let url = `https://mangadex.org/api/v2/manga/${manga.mangaId}`;
let mangaInfo = (await $.get(url)).data;
let status = statuses[manga.followType];
let muID, alID, apSlug, kitsuID, malID;
if (mangaInfo.links) {
muID = mangaInfo.links.mu ? mangaInfo.links.mu : 0;
alID = mangaInfo.links.al ? mangaInfo.links.al : 0;
apSlug = mangaInfo.links.ap ? mangaInfo.links.ap : 0;
kitsuID = mangaInfo.links.kt ? mangaInfo.links.kt : 0;
malID = mangaInfo.links.mal ? mangaInfo.links.mal : 0;
}
let rating = manga.rating ? manga.rating : 0;
return {
mangaTitle: htmlDecode(mangaInfo.title),
status: status,
rating: rating,
muID: muID,
alID: alID,
apSlug: apSlug,
kitsuID: kitsuID,
malID: malID,
mdID: manga.mangaId,
volume: manga.volume,
chapter: manga.chapter,
};
};
// Returns a Promise that resolves after "ms" Milliseconds
var timer = (ms) => {
return new Promise((res) => setTimeout(res, ms));
};
// seconds to HH:MM:SS
var formatSeconds = (seconds) => {
return new Date(seconds * 1000).toISOString().substr(11, 8);
};
var htmlDecode = (value) => {
return $('').html(value).text();
};