// ==UserScript==
// @name Mangadex API v5 reader
// @namespace Violentmonkey Scripts
// @match https://*.mangadex.org/*
// @grant none
// @run-at document-start
// @version 1.10
// @author -
// @description 5/8/2021, 10:46:50 AM
// @downloadURL none
// ==/UserScript==
window.addEventListener('beforescriptexecute', function(e) {
e.stopPropagation();
e.preventDefault();
e.target.remove();
}, true)
window.addEventListener("load",()=>{
window.sessionToken = undefined;
const login = async(username,password)=>{
let response = await fetch("https://api.mangadex.org/auth/login",{
method:"POST",
body:JSON.stringify({
username,
password
}),
headers: {
"Content-Type": "application/json"
}
});
let data = await response.json().catch(e=>alert(`/auth/login failed: ${response.status}`));
if(data.result==="ko") alert("wrong password!");
window.sessionToken = data.token.session;
};
const sleep = (delay)=>new Promise((resolve)=>setTimeout(resolve,delay));
let last_request = performance.now();
let cache = new Map();
window.fetchJSON = async(url)=>{
if(cache.has(url)) return cache.get(url);
let resolve;
let promise = new Promise((r)=>{resolve = r;})
promise.resolve = resolve;
cache.set(url,promise);
// rate limit
while(true) {
let diff = performance.now()-last_request;
let target = diff - 250;
if(target>=0) break;
await sleep(-target);
}
last_request = performance.now();
let headers = {};
if(sessionToken) headers.Authorization = sessionToken;
let options = {
mode: location.href.startsWith("https://api.mangadex.org")?"same-origin":"cors",
headers
};
let response = await fetch(url,options);
try{
let data = await response.json();
promise.resolve(data);
return data;
} catch(e) {
promise.resolve(null);
return promise;
}
};
async function* listIterator(urlstring, params = {}, limit=100) {
let i=0;
params.limit = limit;
while(true) {
let url = new URL(urlstring);
params.offset = i;
url.search = new URLSearchParams(params);
let result = await fetchJSON(url.toString());
if(result===null || result.results.length === 0 || i > result.total) return;
for(let e of result.results) {
yield e;
}
i+=limit;
if(i > result.total) return;
}
}
let getImageUrlsFromChapterID = async(chapterID)=>{
let chapter = await fetchJSON(`https://api.mangadex.org/chapter/${chapterID}`);
let server = await fetchJSON(`https://api.mangadex.org/at-home/server/${chapterID}`);
console.log(chapter)
let urls = chapter.data.attributes.dataSaver.map(s=>`${server.baseUrl}/data-saver/${chapter.data.attributes.hash}/${s}`)
return urls;
};
let getChaptersFromMangaID = async(mangaID)=>{
let chapters = [];
for await (let result of listIterator(`https://api.mangadex.org/manga/${mangaID}/feed`,{"translatedLanguage[]":["en"]})) {
chapters.push(result)
}
let string = (c)=>(c.data.attributes.volume||"0")+"."+(c.data.attributes.chapter||"0");
return chapters.sort((a,b)=>string(b).localeCompare(string(a), undefined, {numeric: true}));
};
let getGroupNamesFromChapter = async(chapter)=>{
let groupIDs = chapter.relationships.filter(e=>e.type==="scanlation_group").map(e=>e.id);
let groups = await Promise.all(groupIDs.map(id=>fetchJSON(`https://api.mangadex.org/group/${id}`)));
return groups.map(g=>g.data.attributes.name);
};
let search = async(title)=>{
let matches = [];
for await (let result of listIterator("https://api.mangadex.org/manga",{title})) {
let resultTitle = result.data.attributes.title.en;
if(resultTitle.toLowerCase().indexOf(title.toLowerCase())>=0) matches.push(result);
}
return matches;
};
const displayChapter = async(chapter)=>{
searchResults.innerHTML = "";
let imageIDs = await getImageUrlsFromChapterID(chapter.data.id);
let mangaID = chapter.relationships.filter(e=>e.type==="manga").map(e=>e.id)[0];
const links = ()=>{
let manga = document.createElement("div");
manga.innerText = "manga";
manga.addEventListener("click", ()=>{
displayManga(mangaID);
});
searchResults.appendChild(manga);
let chapterNumber = document.createElement("div");
chapterNumber.innerText = " chapter: "+chapter.data.attributes.chapter;
searchResults.appendChild(chapterNumber);
};
links();
for(let e of imageIDs) {
let image = document.createElement("img");
image.src = e;
searchResults.appendChild(image);
}
links();
};
const displayManga = async(id)=>{
searchResults.innerHTML = "";
let chapters = await getChaptersFromMangaID(id);
let reads = sessionToken?(await fetchJSON(`https://api.mangadex.org/manga/${id}/read`)).data:[];
for(let e of chapters) {
let chapter = document.createElement("div");
let read = "";
if(sessionToken) read = reads.includes(e.data.id)?"[read] ":"[ ] ";
getGroupNamesFromChapter(e).then((names)=>{
chapter.innerText = `${read}${e.data.attributes.volume||0}.${e.data.attributes.chapter||0} ${e.data.attributes.title} by ${names.join(" ")} `;
});
chapter.addEventListener("click",()=>{
displayChapter(e);
});
searchResults.appendChild(chapter);
}
};
const displayMangaList = async(mangaList)=>{
searchResults.innerHTML = "";
for(let e of mangaList) {
let id = e.data.id;
let title = e.data.attributes.title.en;
let lastChapter = "";
if(e.data.attributes.lastChapter && e.data.attributes.lastChapter!=="0") lastChapter = `[last chapter: ${e.data.attributes.lastVolume||0}.${e.data.attributes.lastChapter}]`;
let manga = document.createElement("div");
manga.innerText = `${title} ${lastChapter}`;
manga.addEventListener("click",()=>{
displayManga(id);
});
searchResults.appendChild(manga);
}
};
let div = document.createElement("div");
div.innerHTML = `