diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/models/Chapter.java b/app/src/main/java/eu/kanade/mangafeed/data/database/models/Chapter.java index a89855557..b8fdacaa9 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/database/models/Chapter.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/models/Chapter.java @@ -4,6 +4,7 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; import eu.kanade.mangafeed.data.database.tables.ChapterTable; +import eu.kanade.mangafeed.util.UrlUtil; @StorIOSQLiteType(table = ChapterTable.TABLE) public class Chapter { @@ -44,6 +45,10 @@ public class Chapter { public Chapter() {} + public void setUrl(String url) { + this.url = UrlUtil.getPath(url); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/models/Manga.java b/app/src/main/java/eu/kanade/mangafeed/data/database/models/Manga.java index c51eeba1e..780cbc0a3 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/database/models/Manga.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/models/Manga.java @@ -4,6 +4,7 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; import eu.kanade.mangafeed.data.database.tables.MangaTable; +import eu.kanade.mangafeed.util.UrlUtil; @StorIOSQLiteType(table = MangaTable.TABLE) public class Manga { @@ -57,6 +58,10 @@ public class Manga { public Manga() {} + public void setUrl(String url) { + this.url = UrlUtil.getPath(url); + } + public static void copyFromNetwork(Manga local, Manga network) { if (network.title != null) local.title = network.title; diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/SourceManager.java b/app/src/main/java/eu/kanade/mangafeed/data/source/SourceManager.java index 35098ec66..36cde5e29 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/SourceManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/SourceManager.java @@ -8,6 +8,7 @@ import java.util.List; import eu.kanade.mangafeed.data.source.base.Source; import eu.kanade.mangafeed.data.source.online.english.Batoto; +import eu.kanade.mangafeed.data.source.online.english.Kissmanga; import eu.kanade.mangafeed.data.source.online.english.Mangafox; import eu.kanade.mangafeed.data.source.online.english.Mangahere; @@ -16,6 +17,7 @@ public class SourceManager { public static final int BATOTO = 1; public static final int MANGAHERE = 2; public static final int MANGAFOX = 3; + public static final int KISSMANGA = 4; private HashMap mSourcesMap; private Context context; @@ -42,6 +44,8 @@ public class SourceManager { return new Mangahere(context); case MANGAFOX: return new Mangafox(context); + case KISSMANGA: + return new Kissmanga(context); } return null; @@ -51,6 +55,7 @@ public class SourceManager { mSourcesMap.put(BATOTO, createSource(BATOTO)); mSourcesMap.put(MANGAHERE, createSource(MANGAHERE)); mSourcesMap.put(MANGAFOX, createSource(MANGAFOX)); + mSourcesMap.put(KISSMANGA, createSource(KISSMANGA)); } public List getSources() { diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/base/BaseSource.java b/app/src/main/java/eu/kanade/mangafeed/data/source/base/BaseSource.java index a12a7cf90..4d65b037f 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/base/BaseSource.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/base/BaseSource.java @@ -20,6 +20,9 @@ public abstract class BaseSource { // Id of the source (must be declared and obtained from SourceManager to avoid conflicts) public abstract int getSourceId(); + // Base url of the source, like: http://example.com + public abstract String getBaseUrl(); + // True if the source requires a login public abstract boolean isLoginRequired(); @@ -76,7 +79,7 @@ public abstract class BaseSource { } // Get the URL of the first page that contains a source image and the page list - protected String overrideChapterPageUrl(String defaultPageUrl) { + protected String overrideChapterUrl(String defaultPageUrl) { return defaultPageUrl; } diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/base/Source.java b/app/src/main/java/eu/kanade/mangafeed/data/source/base/Source.java index 3e25bcf87..67597cc04 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/base/Source.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/base/Source.java @@ -2,6 +2,7 @@ package eu.kanade.mangafeed.data.source.base; import android.content.Context; +import com.bumptech.glide.load.model.LazyHeaders; import com.squareup.okhttp.Headers; import com.squareup.okhttp.Response; @@ -9,6 +10,7 @@ import org.jsoup.Jsoup; import java.util.ArrayList; import java.util.List; +import java.util.Map; import javax.inject.Inject; @@ -25,14 +27,16 @@ import rx.schedulers.Schedulers; public abstract class Source extends BaseSource { - @Inject protected NetworkHelper mNetworkService; - @Inject protected CacheManager mCacheManager; + @Inject protected NetworkHelper networkService; + @Inject protected CacheManager cacheManager; @Inject protected PreferencesHelper prefs; - protected Headers mRequestHeaders; + protected Headers requestHeaders; + protected LazyHeaders glideHeaders; public Source(Context context) { App.get(context).getComponent().inject(this); - mRequestHeaders = headersBuilder().build(); + requestHeaders = headersBuilder().build(); + glideHeaders = glideHeadersBuilder().build(); } // Get the most popular mangas from the source @@ -40,8 +44,8 @@ public abstract class Source extends BaseSource { if (page.page == 1) page.url = getInitialPopularMangasUrl(); - return mNetworkService - .getStringResponse(page.url, mRequestHeaders, null) + return networkService + .getStringResponse(page.url, requestHeaders, null) .map(Jsoup::parse) .doOnNext(doc -> page.mangas = parsePopularMangasFromHtml(doc)) .doOnNext(doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page)) @@ -53,8 +57,8 @@ public abstract class Source extends BaseSource { if (page.page == 1) page.url = getInitialSearchUrl(query); - return mNetworkService - .getStringResponse(page.url, mRequestHeaders, null) + return networkService + .getStringResponse(page.url, requestHeaders, null) .map(Jsoup::parse) .doOnNext(doc -> page.mangas = parseSearchFromHtml(doc)) .doOnNext(doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query)) @@ -63,21 +67,21 @@ public abstract class Source extends BaseSource { // Get manga details from the source public Observable pullMangaFromNetwork(final String mangaUrl) { - return mNetworkService - .getStringResponse(overrideMangaUrl(mangaUrl), mRequestHeaders, null) + return networkService + .getStringResponse(getBaseUrl() + overrideMangaUrl(mangaUrl), requestHeaders, null) .flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml))); } // Get chapter list of a manga from the source - public Observable> pullChaptersFromNetwork(String mangaUrl) { - return mNetworkService - .getStringResponse(mangaUrl, mRequestHeaders, null) + public Observable> pullChaptersFromNetwork(final String mangaUrl) { + return networkService + .getStringResponse(getBaseUrl() + mangaUrl, requestHeaders, null) .flatMap(unparsedHtml -> Observable.just(parseHtmlToChapters(unparsedHtml))); } public Observable> getCachedPageListOrPullFromNetwork(final String chapterUrl) { - return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) + return cacheManager.getPageUrlsFromDiskCache(getChapterCacheKey(chapterUrl)) .onErrorResumeNext(throwable -> { return pullPageListFromNetwork(chapterUrl); }) @@ -85,8 +89,8 @@ public abstract class Source extends BaseSource { } public Observable> pullPageListFromNetwork(final String chapterUrl) { - return mNetworkService - .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) + return networkService + .getStringResponse(getBaseUrl() + overrideChapterUrl(chapterUrl), requestHeaders, null) .flatMap(unparsedHtml -> { List pageUrls = parseHtmlToPageUrls(unparsedHtml); return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); @@ -102,8 +106,8 @@ public abstract class Source extends BaseSource { public Observable getImageUrlFromPage(final Page page) { page.setStatus(Page.LOAD_PAGE); - return mNetworkService - .getStringResponse(overridePageUrl(page.getUrl()), mRequestHeaders, null) + return networkService + .getStringResponse(overridePageUrl(page.getUrl()), requestHeaders, null) .flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml))) .onErrorResumeNext(e -> { page.setStatus(Page.ERROR); @@ -123,13 +127,13 @@ public abstract class Source extends BaseSource { return pageObservable .flatMap(p -> { - if (!mCacheManager.isImageInCache(page.getImageUrl())) { + if (!cacheManager.isImageInCache(page.getImageUrl())) { return cacheImage(page); } return Observable.just(page); }) .flatMap(p -> { - page.setImagePath(mCacheManager.getImagePath(page.getImageUrl())); + page.setImagePath(cacheManager.getImagePath(page.getImageUrl())); page.setStatus(Page.READY); return Observable.just(page); }) @@ -143,7 +147,7 @@ public abstract class Source extends BaseSource { page.setStatus(Page.DOWNLOAD_IMAGE); return getImageProgressResponse(page) .flatMap(resp -> { - if (!mCacheManager.putImageToDiskCache(page.getImageUrl(), resp)) { + if (!cacheManager.putImageToDiskCache(page.getImageUrl(), resp)) { throw new IllegalStateException("Unable to save image"); } return Observable.just(page); @@ -151,12 +155,12 @@ public abstract class Source extends BaseSource { } public Observable getImageProgressResponse(final Page page) { - return mNetworkService.getProgressResponse(page.getImageUrl(), mRequestHeaders, page); + return networkService.getProgressResponse(page.getImageUrl(), requestHeaders, page); } public void savePageList(String chapterUrl, List pages) { if (pages != null) - mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages); + cacheManager.putPageUrlsToDiskCache(getChapterCacheKey(chapterUrl), pages); } protected List convertToPages(List pageUrls) { @@ -174,4 +178,21 @@ public abstract class Source extends BaseSource { return pages; } + protected String getChapterCacheKey(String chapterUrl) { + return getSourceId() + chapterUrl; + } + + protected LazyHeaders.Builder glideHeadersBuilder() { + LazyHeaders.Builder builder = new LazyHeaders.Builder(); + for (Map.Entry> entry : requestHeaders.toMultimap().entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue().get(0)); + } + + return builder; + } + + public LazyHeaders getGlideHeaders() { + return glideHeaders; + } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Batoto.java b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Batoto.java index 55a4cea44..b46fbf750 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Batoto.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Batoto.java @@ -1,6 +1,7 @@ package eu.kanade.mangafeed.data.source.online.english; import android.content.Context; +import android.net.Uri; import com.squareup.okhttp.FormEncodingBuilder; import com.squareup.okhttp.Headers; @@ -33,13 +34,12 @@ public class Batoto extends Source { public static final String NAME = "Batoto (EN)"; public static final String BASE_URL = "http://bato.to"; - public static final String INITIAL_POPULAR_MANGAS_URL = - "http://bato.to/search_ajax?order_cond=views&order=desc&p="; - public static final String INITIAL_SEARCH_URL = "http://bato.to/search_ajax?"; - public static final String INITIAL_PAGE_URL = "http://bato.to/areader?"; - public static final String LOGIN_URL = - "https://bato.to/forums/index.php?app=core&module=global§ion=login"; - + public static final String POPULAR_MANGAS_URL = BASE_URL + "/search_ajax?order_cond=views&order=desc&p=%d"; + public static final String SEARCH_URL = BASE_URL + "/search_ajax?name=%s&p=%s"; + public static final String CHAPTER_URL = "/areader?id=%s&p=1"; + public static final String PAGE_URL = BASE_URL + "/areader?id=%s&p=%s"; + public static final String MANGA_URL = "/comic_pop?id=%s"; + public static final String LOGIN_URL = BASE_URL + "/forums/index.php?app=core&module=global§ion=login"; public Batoto(Context context) { super(context); @@ -50,6 +50,16 @@ public class Batoto extends Source { return NAME; } + @Override + public int getSourceId() { + return SourceManager.BATOTO; + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + @Override protected Headers.Builder headersBuilder() { Headers.Builder builder = super.headersBuilder(); @@ -103,11 +113,6 @@ public class Batoto extends Source { return Observable.just(genres); } - @Override - public int getSourceId() { - return SourceManager.BATOTO; - } - @Override public boolean isLoginRequired() { return true; @@ -115,24 +120,24 @@ public class Batoto extends Source { @Override public String getInitialPopularMangasUrl() { - return INITIAL_POPULAR_MANGAS_URL + "1"; + return String.format(POPULAR_MANGAS_URL, 1); } @Override public String getInitialSearchUrl(String query) { - return INITIAL_SEARCH_URL + "name=" + query + "&p=1"; + return String.format(SEARCH_URL, Uri.encode(query), 1); } @Override protected String overrideMangaUrl(String defaultMangaUrl) { String mangaId = defaultMangaUrl.substring(defaultMangaUrl.lastIndexOf("r") + 1); - return "http://bato.to/comic_pop?id=" + mangaId; + return String.format(MANGA_URL, mangaId); } @Override - protected String overrideChapterPageUrl(String defaultPageUrl) { + protected String overrideChapterUrl(String defaultPageUrl) { String id = defaultPageUrl.substring(defaultPageUrl.indexOf("#") + 1); - return INITIAL_PAGE_URL + "id=" + id + "&p=1"; + return String.format(CHAPTER_URL, id); } @Override @@ -140,7 +145,7 @@ public class Batoto extends Source { int start = defaultPageUrl.indexOf("#") + 1; int end = defaultPageUrl.indexOf("_", start); String id = defaultPageUrl.substring(start, end); - return INITIAL_PAGE_URL + "id=" + id + "&p=" + defaultPageUrl.substring(end+1); + return String.format(PAGE_URL, id, defaultPageUrl.substring(end+1)); } @Override @@ -167,7 +172,7 @@ public class Batoto extends Source { if (next == null) return null; - return INITIAL_POPULAR_MANGAS_URL + (page.page + 1); + return String.format(POPULAR_MANGAS_URL, page.page + 1); } @Override @@ -192,22 +197,16 @@ public class Batoto extends Source { Manga mangaFromHtmlBlock = new Manga(); Element urlElement = htmlBlock.select("a[href^=http://bato.to]").first(); - Element nameElement = urlElement; Element updateElement = htmlBlock.select("td").get(5); mangaFromHtmlBlock.source = getSourceId(); if (urlElement != null) { - String fieldUrl = urlElement.attr("href"); - mangaFromHtmlBlock.url = fieldUrl; - } - if (nameElement != null) { - String fieldName = nameElement.text().trim(); - mangaFromHtmlBlock.title = fieldName; + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); + mangaFromHtmlBlock.title = urlElement.text().trim(); } if (updateElement != null) { - long fieldUpdate = parseUpdateFromElement(updateElement); - mangaFromHtmlBlock.last_update = fieldUpdate; + mangaFromHtmlBlock.last_update = parseUpdateFromElement(updateElement); } return mangaFromHtmlBlock; @@ -219,7 +218,7 @@ public class Batoto extends Source { if (next == null) return null; - return INITIAL_SEARCH_URL + "name=" + query + "&p=" + (page.page + 1); + return String.format(SEARCH_URL, query, page.page + 1); } private long parseUpdateFromElement(Element updateElement) { @@ -257,8 +256,7 @@ public class Batoto extends Source { } } if (descriptionElement != null) { - String fieldDescription = descriptionElement.text().substring("Description:".length()).trim(); - newManga.description = fieldDescription; + newManga.description = descriptionElement.text().substring("Description:".length()).trim(); } if (genreElements != null) { String fieldGenres = ""; @@ -274,8 +272,7 @@ public class Batoto extends Source { newManga.genre = fieldGenres; } if (thumbnailUrlElement != null) { - String fieldThumbnailUrl = thumbnailUrlElement.attr("src"); - newManga.thumbnail_url = fieldThumbnailUrl; + newManga.thumbnail_url = thumbnailUrlElement.attr("src"); } boolean fieldCompleted = unparsedHtml.contains("Complete"); @@ -309,20 +306,16 @@ public class Batoto extends Source { Chapter newChapter = Chapter.create(); Element urlElement = chapterElement.select("a[href^=http://bato.to/reader").first(); - Element nameElement = urlElement; Element dateElement = chapterElement.select("td").get(4); if (urlElement != null) { String fieldUrl = urlElement.attr("href"); - newChapter.url = fieldUrl; - } - if (nameElement != null) { - String fieldName = nameElement.text().trim(); - newChapter.name = fieldName; + newChapter.setUrl(fieldUrl); + newChapter.name = urlElement.text().trim(); + } if (dateElement != null) { - long fieldDate = parseDateFromElement(dateElement); - newChapter.date_upload = fieldDate; + newChapter.date_upload = parseDateFromElement(dateElement); } newChapter.date_fetch = new Date().getTime(); @@ -357,12 +350,8 @@ public class Batoto extends Source { } } else { // For webtoons in one page - Element page = parsedDocument.select("div > a").first(); - String url = page.attr("href"); - url = BASE_URL + "/reader" + url.substring(0, url.length() - 1) + "f"; - for (int i = 0; i < parsedDocument.select("div > img").size(); i++) { - pageUrlList.add(url); + pageUrlList.add(""); } } @@ -401,7 +390,7 @@ public class Batoto extends Source { @Override public Observable login(String username, String password) { - return mNetworkService.getStringResponse(LOGIN_URL, mRequestHeaders, null) + return networkService.getStringResponse(LOGIN_URL, requestHeaders, null) .flatMap(response -> doLogin(response, username, password)) .map(this::isAuthenticationSuccessful); } @@ -420,7 +409,7 @@ public class Batoto extends Source { formBody.add("invisible", "1"); formBody.add("rememberMe", "1"); - return mNetworkService.postData(postUrl, formBody.build(), mRequestHeaders); + return networkService.postData(postUrl, formBody.build(), requestHeaders); } @Override @@ -431,7 +420,7 @@ public class Batoto extends Source { @Override public boolean isLogged() { try { - for ( HttpCookie cookie : mNetworkService.getCookies().get(new URI(BASE_URL)) ) { + for ( HttpCookie cookie : networkService.getCookies().get(new URI(BASE_URL)) ) { if (cookie.getName().equals("pass_hash")) return true; } diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Kissmanga.java b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Kissmanga.java new file mode 100644 index 000000000..f59161aec --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Kissmanga.java @@ -0,0 +1,257 @@ +package eu.kanade.mangafeed.data.source.online.english; + +import android.content.Context; +import android.net.Uri; + +import com.squareup.okhttp.FormEncodingBuilder; +import com.squareup.okhttp.Headers; +import com.squareup.okhttp.Response; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import eu.kanade.mangafeed.data.database.models.Chapter; +import eu.kanade.mangafeed.data.database.models.Manga; +import eu.kanade.mangafeed.data.source.SourceManager; +import eu.kanade.mangafeed.data.source.base.Source; +import eu.kanade.mangafeed.data.source.model.MangasPage; +import eu.kanade.mangafeed.data.source.model.Page; +import rx.Observable; + +public class Kissmanga extends Source { + + public static final String NAME = "Kissmanga (EN)"; + public static final String HOST = "kissmanga.com"; + public static final String IP = "93.174.95.110"; + public static final String BASE_URL = "http://" + IP; + public static final String POPULAR_MANGAS_URL = BASE_URL + "/MangaList/MostPopular?page=%s"; + + public Kissmanga(Context context) { + super(context); + } + + @Override + protected Headers.Builder headersBuilder() { + Headers.Builder builder = super.headersBuilder(); + builder.add("Host", HOST); + return builder; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public int getSourceId() { + return SourceManager.KISSMANGA; + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + + @Override + public boolean isLoginRequired() { + return false; + } + + @Override + protected String getInitialPopularMangasUrl() { + return String.format(POPULAR_MANGAS_URL, 1); + } + + @Override + protected String getInitialSearchUrl(String query) { + return null; + } + + @Override + protected List parsePopularMangasFromHtml(Document parsedHtml) { + List mangaList = new ArrayList<>(); + + Elements mangaHtmlBlocks = parsedHtml.select("table.listing tr:gt(1)"); + for (Element currentHtmlBlock : mangaHtmlBlocks) { + Manga currentManga = constructPopularMangaFromHtmlBlock(currentHtmlBlock); + mangaList.add(currentManga); + } + + return mangaList; + } + + private Manga constructPopularMangaFromHtmlBlock(Element htmlBlock) { + Manga mangaFromHtmlBlock = new Manga(); + mangaFromHtmlBlock.source = getSourceId(); + + Element urlElement = htmlBlock.select("td a:eq(0)").first(); + + if (urlElement != null) { + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); + mangaFromHtmlBlock.title = urlElement.text(); + } + + return mangaFromHtmlBlock; + } + + @Override + protected String parseNextPopularMangasUrl(Document parsedHtml, MangasPage page) { + Element next = parsedHtml.select("li > a:contains(› Next)").first(); + if (next == null) + return null; + + return String.format(POPULAR_MANGAS_URL, next.attr("href")); + } + + @Override + protected List parseSearchFromHtml(Document parsedHtml) { + return null; + } + + @Override + protected String parseNextSearchUrl(Document parsedHtml, MangasPage page, String query) { + return null; + } + + @Override + protected Manga parseHtmlToManga(String mangaUrl, String unparsedHtml) { + Document parsedDocument = Jsoup.parse(unparsedHtml); + + Element infoElement = parsedDocument.select("div.barContent").first(); + Element titleElement = infoElement.select("a.bigChar").first(); + Element authorElement = infoElement.select("p:has(span:contains(Author:)) > a").first(); + Element genreElement = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").first(); + Elements descriptionElement = infoElement.select("p:has(span:contains(Summary:)) ~ p"); + Element thumbnailUrlElement = parsedDocument.select(".rightBox:eq(0) img").first(); + + Manga newManga = new Manga(); + newManga.url = mangaUrl; + + if (titleElement != null) { + newManga.title = titleElement.text(); + } + if (authorElement != null) { + newManga.author = authorElement.text(); + } + if (descriptionElement != null) { + newManga.description = descriptionElement.text(); + } + if (genreElement != null) { + newManga.genre = genreElement.text(); + } + if (thumbnailUrlElement != null) { + newManga.thumbnail_url = Uri.parse(thumbnailUrlElement.attr("src")) + .buildUpon().authority(IP).toString(); + } +// if (statusElement != null) { +// boolean fieldCompleted = statusElement.text().contains("Completed"); +// newManga.status = fieldCompleted + ""; +// } + + newManga.initialized = true; + + return newManga; + } + + @Override + protected List parseHtmlToChapters(String unparsedHtml) { + Document parsedDocument = Jsoup.parse(unparsedHtml); + + List chapterList = new ArrayList<>(); + + Elements chapterElements = parsedDocument.select("table.listing tr:gt(1)"); + for (Element chapterElement : chapterElements) { + Chapter currentChapter = constructChapterFromHtmlBlock(chapterElement); + + chapterList.add(currentChapter); + } + + return chapterList; + } + + private Chapter constructChapterFromHtmlBlock(Element chapterElement) { + Chapter newChapter = Chapter.create(); + + Element urlElement = chapterElement.select("a").first(); + Element dateElement = chapterElement.select("td:eq(1)").first(); + + if (urlElement != null) { + newChapter.setUrl(urlElement.attr("href")); + newChapter.name = urlElement.text(); + } + if (dateElement != null) { + try { + newChapter.date_upload = new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH).parse(dateElement.text()).getTime(); + } catch (ParseException e) { + // Do Nothing. + } + } + + newChapter.date_fetch = new Date().getTime(); + + return newChapter; + } + + public Observable> pullPageListFromNetwork(final String chapterUrl) { + FormEncodingBuilder builder = new FormEncodingBuilder(); + return networkService + .postData(getBaseUrl() + overrideChapterUrl(chapterUrl), builder.build(), requestHeaders) + .flatMap(networkService::mapResponseToString) + .flatMap(unparsedHtml -> { + List pageUrls = parseHtmlToPageUrls(unparsedHtml); + return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); + }); + } + + @Override + protected List parseHtmlToPageUrls(String unparsedHtml) { + Document parsedDocument = Jsoup.parse(unparsedHtml); + List pageUrlList = new ArrayList<>(); + + int numImages = parsedDocument.select("#divImage img").size(); + + for (int i = 0; i < numImages; i++) { + pageUrlList.add(""); + } + return pageUrlList; + } + + @Override + protected List getFirstImageFromPageUrls(List pageUrls, String unparsedHtml) { + List pages = convertToPages(pageUrls); + + Pattern p = Pattern.compile("lstImages.push\\(\"(.+?)\""); + Matcher m = p.matcher(unparsedHtml); + List imageUrls = new ArrayList<>(); + while (m.find()) { + imageUrls.add(m.group(1)); + } + + for (int i = 0; i < pages.size(); i++) { + pages.get(i).setImageUrl(imageUrls.get(i)); + } + return pages; + } + + @Override + protected String parseHtmlToImageUrl(String unparsedHtml) { + return null; + } + + @Override + public Observable getImageProgressResponse(final Page page) { + return networkService.getProgressResponse(page.getImageUrl(), null, page); + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangafox.java b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangafox.java index 217c50d7b..59b8ec6a7 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangafox.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangafox.java @@ -1,6 +1,7 @@ package eu.kanade.mangafeed.data.source.online.english; import android.content.Context; +import android.net.Uri; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -25,9 +26,9 @@ public class Mangafox extends Source { public static final String NAME = "Mangafox (EN)"; public static final String BASE_URL = "http://mangafox.me"; - public static final String INITIAL_POPULAR_MANGAS_URL = "http://mangafox.me/directory/"; - public static final String INITIAL_SEARCH_URL = - "http://mangafox.me/search.php?name_method=cw&advopts=1&order=az&sort=name"; + public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s"; + public static final String SEARCH_URL = + BASE_URL + "/search.php?name_method=cw&advopts=1&order=az&sort=name&name=%s&page=%s"; public Mangafox(Context context) { super(context); @@ -43,6 +44,11 @@ public class Mangafox extends Source { return SourceManager.MANGAFOX; } + @Override + public String getBaseUrl() { + return BASE_URL; + } + @Override public boolean isLoginRequired() { return false; @@ -50,12 +56,12 @@ public class Mangafox extends Source { @Override protected String getInitialPopularMangasUrl() { - return INITIAL_POPULAR_MANGAS_URL; + return String.format(POPULAR_MANGAS_URL, ""); } @Override protected String getInitialSearchUrl(String query) { - return INITIAL_SEARCH_URL + "&name=" + query + "&page=1"; + return String.format(SEARCH_URL, Uri.encode(query), 1); } @Override @@ -78,7 +84,7 @@ public class Mangafox extends Source { Element urlElement = htmlBlock.select("a.title").first(); if (urlElement != null) { - mangaFromHtmlBlock.url = urlElement.attr("href"); + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); mangaFromHtmlBlock.title = urlElement.text(); } @@ -91,7 +97,7 @@ public class Mangafox extends Source { if (next == null) return null; - return INITIAL_POPULAR_MANGAS_URL + next.attr("href"); + return String.format(POPULAR_MANGAS_URL, next.attr("href")); } @Override @@ -114,7 +120,7 @@ public class Mangafox extends Source { Element urlElement = htmlBlock.select("a.series_preview").first(); if (urlElement != null) { - mangaFromHtmlBlock.url = urlElement.attr("href"); + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); mangaFromHtmlBlock.title = urlElement.text(); } @@ -153,24 +159,19 @@ public class Mangafox extends Source { newManga.title = title; } if (artistElement != null) { - String fieldArtist = artistElement.text(); - newManga.artist = fieldArtist; + newManga.artist = artistElement.text(); } if (authorElement != null) { - String fieldAuthor = authorElement.text(); - newManga.author = fieldAuthor; + newManga.author = authorElement.text(); } if (descriptionElement != null) { - String fieldDescription = descriptionElement.text(); - newManga.description = fieldDescription; + newManga.description = descriptionElement.text(); } if (genreElement != null) { - String fieldGenre = genreElement.text(); - newManga.genre = fieldGenre; + newManga.genre = genreElement.text(); } if (thumbnailUrlElement != null) { - String fieldThumbnailUrl = thumbnailUrlElement.attr("src"); - newManga.thumbnail_url = fieldThumbnailUrl; + newManga.thumbnail_url = thumbnailUrlElement.attr("src"); } // if (statusElement != null) { // boolean fieldCompleted = statusElement.text().contains("Completed"); @@ -206,7 +207,7 @@ public class Mangafox extends Source { Element dateElement = chapterElement.select("span.date").first(); if (urlElement != null) { - newChapter.url = urlElement.attr("href"); + newChapter.setUrl(urlElement.attr("href")); } if (nameElement != null) { newChapter.name = nameElement.text(); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangahere.java b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangahere.java index 566aa4d9d..788e8f3b1 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangahere.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Mangahere.java @@ -1,6 +1,7 @@ package eu.kanade.mangafeed.data.source.online.english; import android.content.Context; +import android.net.Uri; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -26,9 +27,8 @@ public class Mangahere extends Source { public static final String NAME = "Mangahere (EN)"; public static final String BASE_URL = "http://www.mangahere.co"; - - private static final String INITIAL_POPULAR_MANGAS_URL = "http://www.mangahere.co/directory/"; - private static final String INITIAL_SEARCH_URL = "http://www.mangahere.co/search.php?"; + public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s"; + public static final String SEARCH_URL = BASE_URL + "/search.php?name=%s&page=%s"; public Mangahere(Context context) { super(context); @@ -44,6 +44,11 @@ public class Mangahere extends Source { return SourceManager.MANGAHERE; } + @Override + public String getBaseUrl() { + return BASE_URL; + } + @Override public boolean isLoginRequired() { return false; @@ -51,12 +56,12 @@ public class Mangahere extends Source { @Override protected String getInitialPopularMangasUrl() { - return INITIAL_POPULAR_MANGAS_URL; + return String.format(POPULAR_MANGAS_URL, ""); } @Override protected String getInitialSearchUrl(String query) { - return INITIAL_SEARCH_URL + "name=" + query + "&page=1"; + return String.format(SEARCH_URL, Uri.encode(query), 1); } public Observable> getGenres() { @@ -117,7 +122,7 @@ public class Mangahere extends Source { Element urlElement = htmlBlock.select("div.title > a").first(); if (urlElement != null) { - mangaFromHtmlBlock.url = urlElement.attr("href"); + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); mangaFromHtmlBlock.title = urlElement.attr("title"); } @@ -130,7 +135,7 @@ public class Mangahere extends Source { if (next == null) return null; - return INITIAL_POPULAR_MANGAS_URL + next.attr("href"); + return String.format(POPULAR_MANGAS_URL, next.attr("href")); } @Override @@ -153,7 +158,7 @@ public class Mangahere extends Source { Element urlElement = htmlBlock.select("a.manga_info").first(); if (urlElement != null) { - mangaFromHtmlBlock.url = urlElement.attr("href"); + mangaFromHtmlBlock.setUrl(urlElement.attr("href")); mangaFromHtmlBlock.title = urlElement.text(); } @@ -231,20 +236,16 @@ public class Mangahere extends Source { newManga.url = mangaUrl; if (artistElement != null) { - String fieldArtist = artistElement.text(); - newManga.artist = fieldArtist; + newManga.artist = artistElement.text(); } if (authorElement != null) { - String fieldAuthor = authorElement.text(); - newManga.author = fieldAuthor; + newManga.author = authorElement.text(); } if (descriptionElement != null) { - String fieldDescription = descriptionElement.text().substring(0, descriptionElement.text().length() - "Show less".length()); - newManga.description = fieldDescription; + newManga.description = descriptionElement.text().substring(0, descriptionElement.text().length() - "Show less".length()); } if (genreElement != null) { - String fieldGenre = genreElement.text().substring("Genre(s):".length()); - newManga.genre = fieldGenre; + newManga.genre = genreElement.text().substring("Genre(s):".length()); } if (statusElement != null) { boolean fieldCompleted = statusElement.text().contains("Completed"); @@ -258,8 +259,7 @@ public class Mangahere extends Source { Element thumbnailUrlElement = parsedDocument.select("img").first(); if (thumbnailUrlElement != null) { - String fieldThumbnailUrl = thumbnailUrlElement.attr("src"); - newManga.thumbnail_url = fieldThumbnailUrl; + newManga.thumbnail_url = thumbnailUrlElement.attr("src"); } newManga.initialized = true; @@ -295,16 +295,13 @@ public class Mangahere extends Source { Element dateElement = chapterElement.select("span.right").first(); if (urlElement != null) { - String fieldUrl = urlElement.attr("href"); - newChapter.url = fieldUrl; + newChapter.setUrl(urlElement.attr("href")); } if (nameElement != null) { - String fieldName = nameElement.text(); - newChapter.name = fieldName; + newChapter.name = nameElement.text(); } if (dateElement != null) { - long fieldDate = parseDateFromElement(dateElement); - newChapter.date_upload = fieldDate; + newChapter.date_upload = parseDateFromElement(dateElement); } newChapter.date_fetch = new Date().getTime(); diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java new file mode 100644 index 000000000..d8380ce2b --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java @@ -0,0 +1,78 @@ +package eu.kanade.mangafeed.ui.catalogue; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.model.GlideUrl; + +import java.util.ArrayList; + +import butterknife.Bind; +import butterknife.ButterKnife; +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.database.models.Manga; + +public class CatalogueAdapter extends ArrayAdapter { + + private CatalogueFragment fragment; + private LayoutInflater inflater; + + public CatalogueAdapter(CatalogueFragment fragment) { + super(fragment.getActivity(), 0, new ArrayList<>()); + this.fragment = fragment; + inflater = fragment.getActivity().getLayoutInflater(); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + Manga manga = getItem(position); + + ViewHolder holder; + if (view != null) { + holder = (ViewHolder) view.getTag(); + } else { + view = inflater.inflate(R.layout.item_catalogue, parent, false); + holder = new ViewHolder(view, fragment); + view.setTag(holder); + } + holder.onSetValues(manga); + return view; + } + + static class ViewHolder { + @Bind(R.id.title) TextView title; + @Bind(R.id.author) TextView author; + @Bind(R.id.thumbnail) ImageView thumbnail; + + CatalogueFragment fragment; + + public ViewHolder(View view, CatalogueFragment fragment) { + this.fragment = fragment; + ButterKnife.bind(this, view); + } + + public void onSetValues(Manga manga) { + title.setText(manga.title); + author.setText(manga.author); + + if (manga.thumbnail_url != null) { + GlideUrl url = new GlideUrl(manga.thumbnail_url, + fragment.getPresenter().getSource().getGlideHeaders()); + + Glide.with(fragment) + .load(url) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .into(thumbnail); + } else { + thumbnail.setImageResource(android.R.color.transparent); + } + } + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java index 46fdbc915..89bbd8163 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueFragment.java @@ -14,6 +14,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.model.GlideUrl; import java.util.List; @@ -27,19 +28,16 @@ import eu.kanade.mangafeed.ui.manga.MangaActivity; import eu.kanade.mangafeed.util.PageBundle; import eu.kanade.mangafeed.widget.EndlessScrollListener; import nucleus.factory.RequiresPresenter; -import uk.co.ribot.easyadapter.EasyAdapter; @RequiresPresenter(CataloguePresenter.class) public class CatalogueFragment extends BaseRxFragment { - @Bind(R.id.gridView) GridView manga_list; - + @Bind(R.id.gridView) GridView gridView; @Bind(R.id.progress) ProgressBar progress; + @Bind(R.id.progress_grid) ProgressBar progressGrid; - @Bind(R.id.progress_grid) ProgressBar progress_grid; - - private EasyAdapter adapter; - private EndlessScrollListener scroll_listener; + private CatalogueAdapter adapter; + private EndlessScrollListener scrollListener; private String search; public final static String SOURCE_ID = "source_id"; @@ -107,13 +105,9 @@ public class CatalogueFragment extends BaseRxFragment { } } - public EasyAdapter getAdapter() { - return adapter; - } - public void initializeAdapter() { - adapter = new EasyAdapter<>(getActivity(), CatalogueHolder.class); - manga_list.setAdapter(adapter); + adapter = new CatalogueAdapter(this); + gridView.setAdapter(adapter); } @OnItemClick(R.id.gridView) @@ -126,8 +120,8 @@ public class CatalogueFragment extends BaseRxFragment { } public void initializeScrollListener() { - scroll_listener = new EndlessScrollListener(this::requestNext); - manga_list.setOnScrollListener(scroll_listener); + scrollListener = new EndlessScrollListener(this::requestNext); + gridView.setOnScrollListener(scrollListener); } public void requestNext() { @@ -140,21 +134,22 @@ public class CatalogueFragment extends BaseRxFragment { } public void showGridProgressBar() { - progress_grid.setVisibility(ProgressBar.VISIBLE); + progressGrid.setVisibility(ProgressBar.VISIBLE); } public void hideProgressBar() { progress.setVisibility(ProgressBar.GONE); - progress_grid.setVisibility(ProgressBar.GONE); + progressGrid.setVisibility(ProgressBar.GONE); } public void onAddPage(PageBundle> page) { hideProgressBar(); if (page.page == 0) { - adapter.getItems().clear(); - scroll_listener.resetScroll(); + gridView.setSelection(0); + adapter.clear(); + scrollListener.resetScroll(); } - adapter.addItems(page.data); + adapter.addAll(page.data); } private int getMangaIndex(Manga manga) { @@ -170,8 +165,8 @@ public class CatalogueFragment extends BaseRxFragment { if (position == -1) return null; - View v = manga_list.getChildAt(position - - manga_list.getFirstVisiblePosition()); + View v = gridView.getChildAt(position - + gridView.getFirstVisiblePosition()); if(v == null) return null; @@ -182,8 +177,11 @@ public class CatalogueFragment extends BaseRxFragment { public void updateImage(Manga manga) { ImageView imageView = getImageView(getMangaIndex(manga)); if (imageView != null) { + GlideUrl url = new GlideUrl(manga.thumbnail_url, + getPresenter().getSource().getGlideHeaders()); + Glide.with(this) - .load(manga.thumbnail_url) + .load(url) .centerCrop() .into(imageView); } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueHolder.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueHolder.java deleted file mode 100644 index 12f180676..000000000 --- a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueHolder.java +++ /dev/null @@ -1,47 +0,0 @@ -package eu.kanade.mangafeed.ui.catalogue; - -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; - -import java.util.Objects; - -import eu.kanade.mangafeed.R; -import eu.kanade.mangafeed.data.database.models.Manga; -import uk.co.ribot.easyadapter.ItemViewHolder; -import uk.co.ribot.easyadapter.PositionInfo; -import uk.co.ribot.easyadapter.annotations.LayoutId; -import uk.co.ribot.easyadapter.annotations.ViewId; - -@LayoutId(R.layout.item_catalogue) -public class CatalogueHolder extends ItemViewHolder { - - @ViewId(R.id.title) TextView title; - - @ViewId(R.id.author) TextView author; - - @ViewId(R.id.thumbnail) ImageView thumbnail; - - public CatalogueHolder(View view) { - super(view); - } - - @Override - public void onSetValues(Manga manga, PositionInfo positionInfo) { - title.setText(manga.title); - author.setText(manga.author); - - if (manga.thumbnail_url != null) { - Glide.with(getContext()) - .load(manga.thumbnail_url) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .into(thumbnail); - } else { - thumbnail.setImageResource(android.R.color.transparent); - } - } -} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java index a3f601e50..1aa238aef 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CataloguePresenter.java @@ -201,4 +201,8 @@ public class CataloguePresenter extends BasePresenter { restartRequest(); } + public Source getSource() { + return selectedSource; + } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/util/UrlUtil.java b/app/src/main/java/eu/kanade/mangafeed/util/UrlUtil.java new file mode 100644 index 000000000..2c4ea9997 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/util/UrlUtil.java @@ -0,0 +1,21 @@ +package eu.kanade.mangafeed.util; + +import java.net.URI; +import java.net.URISyntaxException; + +public class UrlUtil { + + public static String getPath(String s) { + try { + URI uri = new URI(s); + String out = uri.getPath(); + if (uri.getQuery() != null) + out += "?" + uri.getQuery(); + if (uri.getFragment() != null) + out += "#" + uri.getFragment(); + return out; + } catch (URISyntaxException e) { + return s; + } + } +} diff --git a/app/src/main/res/layout/item_source.xml b/app/src/main/res/layout/item_source.xml index d45304cc6..d51e8cc5f 100644 --- a/app/src/main/res/layout/item_source.xml +++ b/app/src/main/res/layout/item_source.xml @@ -7,7 +7,7 @@