// ==UserScript== // @name Mangadex API v5 reader // @namespace Violentmonkey Scripts // @match https://*.mangadex.org/* // @grant none // @run-at document-start // @version 1.9 // @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 })}); 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 = `
`; document.body.insertBefore(div,document.body.childNodes[0]) let searchResults = document.querySelector("#search-results"); let search_debounce_last_timeout; document.querySelector("#search").addEventListener("input",async(e)=>{ console.log(e.target.value); clearTimeout(search_debounce_last_timeout); search_debounce_last_timeout = setTimeout(async()=>{ let query = e.target.value; let result = await search(query); displayMangaList(result); },1000); }); document.querySelector("#show-follows").addEventListener("click",async(e)=>{ let username = document.querySelector("#username").value; let password = document.querySelector("#password").value; await login(username,password); let list = []; for await (let result of listIterator(`https://api.mangadex.org/user/follows/manga`)) { list.push(result) } displayMangaList(list); }); });