package org.skylion.mangareader.mangaengine; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.imageio.ImageIO; import org.skylion.mangareader.util.Logger; import org.skylion.mangareader.util.StringUtil; /** * Unofficial API hook for MangaEden.com * This website actually has an API so should be reliable. * @author Skylion * */ public class MangaEdenAPI implements MangaEngine{ private final static String[][] mangaList = initializeMangaList();//This takes a while to populate; private String[] chapterURLs; private String[] pageURLs; private String currentManga; private int currentChapter; private int currentPage; private final static String MANGA_EDEN_URL = "https://www.mangaeden.com/api/"; public MangaEdenAPI() throws IOException { currentManga = this.getMangaURL("Mirai Nikki"); chapterURLs = this.initializeChapterList(); currentChapter = 0; pageURLs = this.initializePageList(); currentPage = 0; } @Override public String getCurrentURL() { return pageURLs[currentPage]; } @Override public void setCurrentURL(String url) { if(url.contains("www.mangaeden.com/api/manga/")){ this.currentManga = url; this.currentChapter = 0; refreshLists(); } else if(url.contains("www.mangaeden.com/api/chapter")){ int chapterIndex = StringUtil.indexOf(chapterURLs, url); if(chapterIndex!=-1){ this.currentChapter = chapterIndex; this.currentPage = 0; refreshLists(); } } else if(url.contains("cdn.mangaeden.com/mangasimg/")){ int pageIndex = StringUtil.indexOf(pageURLs, url); if(pageIndex!=-1) { this.currentPage = pageIndex; } else { String[] pages; try { pages = this.getPageURLs(chapterURLs[this.currentChapter+1]); if(StringUtil.indexOf(pages, url)!=-1){ this.currentChapter++; this.pageURLs = pages; this.currentPage = 0; } refreshLists(); } catch (IOException e) { Logger.log(e); } } } } @Override public BufferedImage loadImg(String url) throws IOException { if(url.contains("manga")){ setCurrentURL(url); refreshLists(); url = this.pageURLs[0]; } BufferedImage image = (getImage(url)); setCurrentURL(url); return image; } @Override public BufferedImage getImage(String url) throws IOException { if(url.contains("chapter")){ url = this.getPageURLs(url)[0]; } return ImageIO.read(new URL(url)); } @Override public String getNextPage() { if(currentPage+1<this.pageURLs.length-1){ return this.pageURLs[currentPage+1]; } else if(currentChapter+1 < chapterURLs.length){ try { return this.getPageURLs(this.chapterURLs[currentChapter+1])[0]; } catch (IOException e) { Logger.log(e); return this.currentManga; } } else { return this.pageURLs[this.currentPage]; } } @Override public String getPreviousPage() { if(currentPage>0){ return this.pageURLs[currentPage-1];//Goes back page } else if(currentChapter>0){ return this.chapterURLs[currentChapter-1];//Else goes back a chapter } else { return this.pageURLs[this.currentPage];//Else does nothing } } @Override public boolean isValidPage(String url) { return url.contains("api") && url.contains("mangaeden.com"); } @Override public List<String> getMangaList() { List<String> out = new ArrayList<String>(mangaList.length); for(int i = 0; i<mangaList.length; i++){ out.add(mangaList[i][0]); } return out; } @Override public String getMangaName() { try { return getNameFromChapter(currentManga); } catch (IOException e) { e.printStackTrace(); return null; } } @Override public String[] getChapterList() { return this.chapterURLs; } @Override public String[] getPageList() { return this.pageURLs; } @Override public String[] getChapterNames() { try { String raw = StringUtil.urlToText(currentManga); raw = raw.substring(raw.indexOf("\"chapters\": [")+13);//Removes unnecessary information raw = raw.substring(0, raw.indexOf("],\n \""));//End of Chapter Section String[] chapters = raw.split("\\],");//Parses into chapters String[] out = new String[chapters.length]; for(int i = 0; i<chapters.length; i++){ String tmp = chapters[i].substring(0, chapters[i].indexOf(',')); out[chapters.length-1-i] = tmp.substring(tmp.lastIndexOf(' ')); } StringUtil.formatChapterNames(out); return out; } catch (IOException e) { return null; } } @Override public String getMangaURL(String mangaName) { mangaName = StringUtil.removeTrailingWhiteSpaces(mangaName); for(int i = 0; i<mangaList.length; i++){ if(mangaList[i][0].equalsIgnoreCase(mangaName)){ System.out.println("Manga#: " + i); return mangaList[i][1]; } } return null; } @Override public int getCurrentPageNum() { return this.currentPage+1; } @Override public int getCurrentChapNum() { return this.currentChapter; } /** * Generates the MangaList * Called once during first instationation of the class. The list is very resource * intensive to construct. Thus, it is only called once. * @return The initialized manga list. */ private static String[][] initializeMangaList(){ String raw = multiPageFetch(25); String[] manga = raw.split("\\},"); String[][] mangaProps = new String[manga.length][7]; for(int i = 0; i<manga.length; i++){ mangaProps[i]=manga[i].split(",\n"); } String[][] out = new String[mangaProps.length][2]; for(int i = 0; i<mangaProps.length; i++){ out[i][0] = extractValue(mangaProps[i][mangaProps[i].length-1]);//Always last value if(mangaProps[i].length<7){//Performs manual search if a value is missing or out of order. for(int x = 0; x<mangaProps[i].length; x++){ if(mangaProps[i][x].contains("\"i\"")){ out[i][1] = extractValue(mangaProps[i][x]); } } } else{ out[i][1] = extractValue(mangaProps[i][2]); } out[i][1] = generateMangaURL(out[i][1]);//Generates the URL from the ID } return out; } private static String multiPageFetch(int pages){ //Uses an executor service pool for concurrency ExecutorService pool = Executors.newFixedThreadPool(pages); //Stores the Future (Data that will be returned in the future) Set<Future<String>> set = new LinkedHashSet<Future<String>>(pages); for(int i = 0; i<= pages; i++){ //Iterates through the list Callable<String> callable = new pageFetcher(i);//Creates Callable Future<String> future = pool.submit(callable);//Begins to run Callable set.add(future);//Adds the response that will be returned to a set. } List<String> strings = new ArrayList<String>(set.size()); for(Future<String> future: set){ try { strings.add(future.get());//Gets the returned data from the future. } catch (ExecutionException e) {//Thrown if the MP3DataFetcher encountered an error. Throwable ex = e.getCause(); ex.printStackTrace(); } catch (InterruptedException e){//Will probably never be called, but just in case... Thread.currentThread().interrupt();//Interrupts the thread since something went wrong. } } return StringUtil.concatCollection(strings);//Sequences the stream. } private static class pageFetcher implements Callable<String>{ private static final String BASE_URL = "https://www.mangaeden.com/api/list/0/"; private int page; public pageFetcher(int page){ this.page = page; } @Override public String call() throws IOException{ String url = BASE_URL + "?p=" + page; String raw = StringUtil.urlToText(url); raw = raw.substring(raw.indexOf(" \"manga\": [")+" \"manga\": [".length()+1, raw.lastIndexOf("],"));//Cleans up raw = raw.substring(1); return raw; } } private String[] initializeChapterList() throws IOException{ return getChapterURLs(this.currentManga); } private String[] getChapterURLs(String mangaURL) throws IOException{ String raw = StringUtil.urlToText(mangaURL); raw = raw.substring(raw.indexOf("\"chapters\": [")+13);//Removes unnecessary information raw = raw.substring(0, raw.indexOf("],\n \""));//End of Chapter Section String[] chapters = raw.split("\\],");//Parses String[] out = new String[chapters.length]; for(int i = 0; i<chapters.length; i++){ chapters[i] = chapters[i].substring(0, chapters[i].lastIndexOf('"')); chapters[i] = chapters[i].substring(chapters[i].lastIndexOf('"')+1); chapters[i] = generateChapterURL(chapters[i]); out[chapters.length-1-i] = chapters[i];//Reverses order } return out; } private String[] initializePageList() throws IOException{ return getPageURLs(this.chapterURLs[this.currentChapter]); } private String[] getPageURLs(String chapterURL) throws IOException{ String raw = StringUtil.urlToText(chapterURL); return parsePageList(raw); } private String[] parsePageList(String raw){ raw = raw.substring(raw.indexOf("\"images\": [") + 15); String[] pages = raw.split("\\]"); String[] out = new String[pages.length]; for(int i = 0; i<pages.length-2; i++){ pages[i] = pages[i].split(",")[2]; pages[i] = pages[i].substring(pages[i].lastIndexOf(' ')+1); if(pages[i].indexOf('"')!=-1){ pages[i] = this.generateImageURL(StringUtil.stripQuotes(pages[i])); } else{ pages[i] = null; } out[pages.length-1-i] = pages[i];//Reverse Array to proper order } pages = out; out = null;//Trash collection pages = StringUtil.cleanArray(pages); return pages; } /** * Generates the Image from the currentURL * @param mangaID The manga ID you want to generate the URL * @return The generated URL */ private static String generateMangaURL(String mangaID){ return MANGA_EDEN_URL + "manga/" + mangaID + "/"; } private String generateChapterURL(String chapterID){ return MANGA_EDEN_URL + "chapter/" + chapterID + "/"; } private String generateImageURL(String imageID){ String out = "http://cdn.mangaeden.com/mangasimg/" + imageID; System.out.println(out.replace("\n", "\\n")); return out; } private static String extractValue(String input){ input = input.replace("{",""); input = input.replace("}", ""); input = input.replace("\n", ""); if(!input.contains(":")){ System.out.println("ERROR" + input); } input = input.substring(input.indexOf(':')+2); input = StringUtil.stripQuotes(input); return input; } public String getNameFromChapter(String mangaURL) throws IOException{ String raw = StringUtil.urlToText(mangaURL); raw = raw.substring(raw.indexOf("\"title\": ")+ 10); raw = raw.substring(0,raw.indexOf('"')); return raw; } private void refreshLists(){ int attempts = 0; while(attempts<3){ try { currentPage = 0; this.chapterURLs = this.initializeChapterList(); this.pageURLs = this.initializePageList(); break; } catch (IOException e) { if(attempts == 2){ e.printStackTrace(); } attempts++; } } System.out.println(Arrays.toString(this.pageURLs)); } }