From cbd2e8398b27ff656abc8ee6bf82b105154e8f99 Mon Sep 17 00:00:00 2001 From: inorichi Date: Mon, 30 Nov 2015 17:54:35 +0100 Subject: [PATCH] Add Kissmanga (search not working yet) --- .../mangafeed/data/source/SourceManager.java | 5 + .../mangafeed/data/source/base/Source.java | 21 +- .../data/source/online/english/Batoto.java | 6 +- .../data/source/online/english/Kissmanga.java | 257 ++++++++++++++++++ .../ui/catalogue/CatalogueAdapter.java | 78 ++++++ .../ui/catalogue/CatalogueFragment.java | 44 ++- .../ui/catalogue/CatalogueHolder.java | 47 ---- .../ui/catalogue/CataloguePresenter.java | 4 + 8 files changed, 385 insertions(+), 77 deletions(-) create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/source/online/english/Kissmanga.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java delete mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueHolder.java 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 35098ec667..36cde5e297 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/Source.java b/app/src/main/java/eu/kanade/mangafeed/data/source/base/Source.java index c76890844e..67597cc042 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; @@ -29,10 +31,12 @@ public abstract class Source extends BaseSource { @Inject protected CacheManager cacheManager; @Inject protected PreferencesHelper prefs; protected Headers requestHeaders; + protected LazyHeaders glideHeaders; public Source(Context context) { App.get(context).getComponent().inject(this); requestHeaders = headersBuilder().build(); + glideHeaders = glideHeadersBuilder().build(); } // Get the most popular mangas from the source @@ -174,8 +178,21 @@ public abstract class Source extends BaseSource { return pages; } - protected String getChapterCacheKey(String chapteUrl) { - return getSourceId() + chapteUrl; + 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 ceedcb3f2e..b46fbf750b 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 @@ -350,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(""); } } 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 0000000000..f59161aec3 --- /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/ui/catalogue/CatalogueAdapter.java b/app/src/main/java/eu/kanade/mangafeed/ui/catalogue/CatalogueAdapter.java new file mode 100644 index 0000000000..d8380ce2b8 --- /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 46fdbc915a..89bbd8163e 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 12f180676c..0000000000 --- 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 a3f601e508..1aa238aef8 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; + } + }