From 1b45ff3b1209b98d7ec299fea1ccbbd0f760315f Mon Sep 17 00:00:00 2001 From: inorichi Date: Mon, 26 Oct 2015 17:29:16 +0100 Subject: [PATCH] Authentication with batoto done --- .../mangafeed/data/helpers/NetworkHelper.java | 37 +++++++++- .../mangafeed/data/helpers/SourceManager.java | 12 +-- .../injection/component/AppComponent.java | 3 + .../injection/module/DataModule.java | 4 +- .../mangafeed/presenter/SourcePresenter.java | 10 +++ .../eu/kanade/mangafeed/sources/Batoto.java | 73 +++++++++++++++++-- .../kanade/mangafeed/sources/MangaHere.java | 8 +- .../mangafeed/sources/base/BaseSource.java | 15 ++++ .../kanade/mangafeed/sources/base/Source.java | 28 ++++--- .../ui/fragment/SettingsAccountsFragment.java | 11 +++ .../mangafeed/ui/fragment/SourceFragment.java | 8 +- .../main/res/layout/pref_account_login.xml | 8 ++ app/src/main/res/values/strings.xml | 1 + 13 files changed, 186 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/NetworkHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/NetworkHelper.java index a1d5f888f8..a581c70cde 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/NetworkHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/NetworkHelper.java @@ -5,22 +5,31 @@ import com.squareup.okhttp.CacheControl; import com.squareup.okhttp.Headers; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.CookieStore; + import rx.Observable; public final class NetworkHelper { private OkHttpClient mClient; + private CookieManager cookieManager; public final CacheControl NULL_CACHE_CONTROL = new CacheControl.Builder().noCache().build(); public final Headers NULL_HEADERS = new Headers.Builder().build(); public NetworkHelper() { mClient = new OkHttpClient(); + cookieManager = new CookieManager(); + cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); + mClient.setCookieHandler(cookieManager); } - public Observable getResponse(final String url, final CacheControl cacheControl, final Headers headers) { + public Observable getResponse(final String url, final Headers headers, final CacheControl cacheControl) { return Observable.create(subscriber -> { try { if (!subscriber.isUnsubscribed()) { @@ -49,10 +58,32 @@ public final class NetworkHelper { }); } - public Observable getStringResponse(final String url, final CacheControl cacheControl, final Headers headers) { + public Observable getStringResponse(final String url, final Headers headers, final CacheControl cacheControl) { - return getResponse(url, cacheControl, headers) + return getResponse(url, headers, cacheControl) .flatMap(this::mapResponseToString); } + public Observable postData(final String url, final RequestBody formBody, final Headers headers) { + return Observable.create(subscriber -> { + try { + if (!subscriber.isUnsubscribed()) { + Request request = new Request.Builder() + .url(url) + .post(formBody) + .headers(headers != null ? headers : NULL_HEADERS) + .build(); + subscriber.onNext(mClient.newCall(request).execute()); + } + subscriber.onCompleted(); + } catch (Throwable e) { + subscriber.onError(e); + } + }); + } + + public CookieStore getCookies() { + return cookieManager.getCookieStore(); + } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java index fcd906ca9c..0444cc167c 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java @@ -1,5 +1,7 @@ package eu.kanade.mangafeed.data.helpers; +import android.content.Context; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -17,11 +19,11 @@ public class SourceManager { private HashMap mSourcesMap; private NetworkHelper mNetworkHelper; private CacheManager mCacheManager; + private Context context; - public SourceManager(NetworkHelper networkHelper, CacheManager cacheManager) { + public SourceManager(Context context) { mSourcesMap = new HashMap<>(); - mNetworkHelper = networkHelper; - mCacheManager = cacheManager; + this.context = context; initializeSources(); } @@ -36,9 +38,9 @@ public class SourceManager { private Source createSource(int sourceKey) { switch (sourceKey) { case BATOTO: - return new Batoto(mNetworkHelper, mCacheManager); + return new Batoto(context); case MANGAHERE: - return new MangaHere(mNetworkHelper, mCacheManager); + return new MangaHere(context); } return null; diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java index 259798c582..0a93bb7550 100644 --- a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java +++ b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java @@ -14,6 +14,7 @@ import eu.kanade.mangafeed.presenter.MangaDetailPresenter; import eu.kanade.mangafeed.presenter.MangaInfoPresenter; import eu.kanade.mangafeed.presenter.ReaderPresenter; import eu.kanade.mangafeed.presenter.SourcePresenter; +import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.ui.activity.ReaderActivity; import eu.kanade.mangafeed.ui.fragment.SettingsAccountsFragment; @@ -37,6 +38,8 @@ public interface AppComponent { void inject(ReaderActivity readerActivity); void inject(SettingsAccountsFragment settingsAccountsFragment); + void inject(Source source); + Application application(); } diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java b/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java index 8359efd0ca..8192622507 100644 --- a/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java +++ b/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java @@ -55,8 +55,8 @@ public class DataModule { @Provides @Singleton - SourceManager provideSourceManager(NetworkHelper networkHelper, CacheManager cacheManager) { - return new SourceManager(networkHelper, cacheManager); + SourceManager provideSourceManager(Application app) { + return new SourceManager(app); } @Provides diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/SourcePresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/SourcePresenter.java index c4e23f484e..3f87ade20f 100644 --- a/app/src/main/java/eu/kanade/mangafeed/presenter/SourcePresenter.java +++ b/app/src/main/java/eu/kanade/mangafeed/presenter/SourcePresenter.java @@ -2,13 +2,16 @@ package eu.kanade.mangafeed.presenter; import javax.inject.Inject; +import eu.kanade.mangafeed.data.helpers.PreferencesHelper; import eu.kanade.mangafeed.data.helpers.SourceManager; +import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.ui.fragment.SourceFragment; public class SourcePresenter extends BasePresenter { @Inject SourceManager sourceManager; + @Inject PreferencesHelper prefs; @Override protected void onTakeView(SourceFragment view) { @@ -17,4 +20,11 @@ public class SourcePresenter extends BasePresenter { view.setItems(sourceManager.getSources()); } + public boolean isValidSource(Source source) { + if (!source.isLoginRequired() || source.isLogged()) + return true; + + return !(prefs.getSourceUsername(source).equals("") + || prefs.getSourcePassword(source).equals("")); + } } diff --git a/app/src/main/java/eu/kanade/mangafeed/sources/Batoto.java b/app/src/main/java/eu/kanade/mangafeed/sources/Batoto.java index dbb6690dfa..7a8fde709e 100644 --- a/app/src/main/java/eu/kanade/mangafeed/sources/Batoto.java +++ b/app/src/main/java/eu/kanade/mangafeed/sources/Batoto.java @@ -1,12 +1,19 @@ package eu.kanade.mangafeed.sources; +import android.content.Context; + +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.net.HttpCookie; +import java.net.URI; +import java.net.URISyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -14,8 +21,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; -import eu.kanade.mangafeed.data.caches.CacheManager; -import eu.kanade.mangafeed.data.helpers.NetworkHelper; import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Manga; @@ -25,15 +30,17 @@ import rx.Observable; public class Batoto extends Source { public static final String NAME = "Batoto (EN)"; - public static final String BASE_URL = "www.bato.to"; + public static final String BASE_URL = "http://bato.to"; public static final String INITIAL_UPDATE_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 Batoto(NetworkHelper networkService, CacheManager cacheManager) { - super(networkService, cacheManager); + public Batoto(Context context) { + super(context); } @Override @@ -336,5 +343,61 @@ public class Batoto extends Source { return imageElement.attr("src"); } + @Override + public Observable login(String username, String password) { + return mNetworkService.getStringResponse(LOGIN_URL, mRequestHeaders, null) + .flatMap(response -> doLogin(response, username, password)) + .map(this::isAuthenticationSuccessful); + } + + private Observable doLogin(String response, String username, String password) { + Document doc = Jsoup.parse(response); + Element form = doc.select("#login").first(); + String postUrl = form.attr("action"); + + FormEncodingBuilder formBody = new FormEncodingBuilder(); + Element authKey = form.select("input[name=auth_key").first(); + + formBody.add(authKey.attr("name"), authKey.attr("value")); + formBody.add("ips_username", username); + formBody.add("ips_password", password); + formBody.add("invisible", "1"); + formBody.add("rememberMe", "1"); + + return mNetworkService.postData(postUrl, formBody.build(), mRequestHeaders); + } + + @Override + protected boolean isAuthenticationSuccessful(Response response) { + return response.priorResponse() != null && response.priorResponse().code() == 302; + } + + @Override + public boolean isLogged() { + try { + for ( HttpCookie cookie : mNetworkService.getCookies().get(new URI(BASE_URL)) ) { + if (cookie.getName().equals("pass_hash")) + return true; + } + + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public Observable> pullChaptersFromNetwork(String mangaUrl) { + Observable> observable; + if (!isLogged()) { + observable = login(prefs.getSourceUsername(this), prefs.getSourcePassword(this)) + .flatMap(result -> super.pullChaptersFromNetwork(mangaUrl)); + } + else { + observable = super.pullChaptersFromNetwork(mangaUrl); + } + return observable; + } + } diff --git a/app/src/main/java/eu/kanade/mangafeed/sources/MangaHere.java b/app/src/main/java/eu/kanade/mangafeed/sources/MangaHere.java index f473ed1476..db8f0e4195 100644 --- a/app/src/main/java/eu/kanade/mangafeed/sources/MangaHere.java +++ b/app/src/main/java/eu/kanade/mangafeed/sources/MangaHere.java @@ -1,5 +1,7 @@ package eu.kanade.mangafeed.sources; +import android.content.Context; + import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -13,8 +15,6 @@ import java.util.Date; import java.util.List; import java.util.Locale; -import eu.kanade.mangafeed.data.caches.CacheManager; -import eu.kanade.mangafeed.data.helpers.NetworkHelper; import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Manga; @@ -29,8 +29,8 @@ public class MangaHere extends Source { private static final String INITIAL_UPDATE_URL = "http://www.mangahere.co/latest/"; private static final String INITIAL_SEARCH_URL = "http://www.mangahere.co/search.php?"; - public MangaHere(NetworkHelper networkService, CacheManager cacheManager) { - super(networkService, cacheManager); + public MangaHere(Context context) { + super(context); } @Override diff --git a/app/src/main/java/eu/kanade/mangafeed/sources/base/BaseSource.java b/app/src/main/java/eu/kanade/mangafeed/sources/base/BaseSource.java index b17d328d92..14fb794169 100644 --- a/app/src/main/java/eu/kanade/mangafeed/sources/base/BaseSource.java +++ b/app/src/main/java/eu/kanade/mangafeed/sources/base/BaseSource.java @@ -1,11 +1,13 @@ package eu.kanade.mangafeed.sources.base; import com.squareup.okhttp.Headers; +import com.squareup.okhttp.Response; import java.util.List; import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Manga; +import rx.Observable; public abstract class BaseSource { @@ -43,6 +45,19 @@ public abstract class BaseSource { protected abstract String parseHtmlToImageUrl(String unparsedHtml); + // Login related methods, shouldn't be overriden if the source doesn't require it + public Observable login(String username, String password) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean isLogged() { + throw new UnsupportedOperationException("Not implemented"); + } + + protected boolean isAuthenticationSuccessful(Response response) { + throw new UnsupportedOperationException("Not implemented"); + } + // Default fields, they can be overriden by sources' implementation diff --git a/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java b/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java index 79ba9af11d..0ddc3b8848 100644 --- a/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java +++ b/app/src/main/java/eu/kanade/mangafeed/sources/base/Source.java @@ -1,13 +1,19 @@ package eu.kanade.mangafeed.sources.base; +import android.content.Context; + import com.squareup.okhttp.Headers; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +import eu.kanade.mangafeed.App; import eu.kanade.mangafeed.data.caches.CacheManager; import eu.kanade.mangafeed.data.helpers.NetworkHelper; +import eu.kanade.mangafeed.data.helpers.PreferencesHelper; import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Page; @@ -16,13 +22,13 @@ import rx.schedulers.Schedulers; public abstract class Source extends BaseSource { - protected NetworkHelper mNetworkService; - protected CacheManager mCacheManager; + @Inject protected NetworkHelper mNetworkService; + @Inject protected CacheManager mCacheManager; + @Inject protected PreferencesHelper prefs; protected Headers mRequestHeaders; - public Source(NetworkHelper networkService, CacheManager cacheManager) { - mNetworkService = networkService; - mCacheManager = cacheManager; + public Source(Context context) { + App.get(context).getComponent().inject(this); mRequestHeaders = headersBuilder().build(); } @@ -30,28 +36,28 @@ public abstract class Source extends BaseSource { public Observable> pullPopularMangasFromNetwork(int page) { String url = getUrlFromPageNumber(page); return mNetworkService - .getStringResponse(url, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(url, mRequestHeaders, null) .flatMap(response -> Observable.just(parsePopularMangasFromHtml(response))); } // Get mangas from the source with a query public Observable> searchMangasFromNetwork(String query, int page) { return mNetworkService - .getStringResponse(getSearchUrl(query, page), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(getSearchUrl(query, page), mRequestHeaders, null) .flatMap(response -> Observable.just(parseSearchFromHtml(response))); } // Get manga details from the source public Observable pullMangaFromNetwork(final String mangaUrl) { return mNetworkService - .getStringResponse(overrideMangaUrl(mangaUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(overrideMangaUrl(mangaUrl), mRequestHeaders, 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, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(mangaUrl, mRequestHeaders, null) .flatMap(unparsedHtml -> Observable.just(parseHtmlToChapters(unparsedHtml))); } @@ -60,7 +66,7 @@ public abstract class Source extends BaseSource { return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) .onErrorResumeNext(throwable -> { return mNetworkService - .getStringResponse(overrideChapterPageUrl(chapterUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(overrideChapterPageUrl(chapterUrl), mRequestHeaders, null) .flatMap(unparsedHtml -> { List pageUrls = parseHtmlToPageUrls(unparsedHtml); return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); @@ -82,7 +88,7 @@ public abstract class Source extends BaseSource { private Observable getImageUrlFromPage(final Page page) { return mNetworkService - .getStringResponse(overrideRemainingPagesUrl(page.getUrl()), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) + .getStringResponse(overrideRemainingPagesUrl(page.getUrl()), mRequestHeaders, null) .flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml))) .flatMap(imageUrl -> { page.setImageUrl(imageUrl); diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SettingsAccountsFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SettingsAccountsFragment.java index 7025bbce1e..80789207b7 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SettingsAccountsFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SettingsAccountsFragment.java @@ -25,6 +25,9 @@ import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.sources.base.Source; import eu.kanade.mangafeed.ui.activity.base.BaseActivity; import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import timber.log.Timber; public class SettingsAccountsFragment extends PreferenceFragment { @@ -112,6 +115,14 @@ public class SettingsAccountsFragment extends PreferenceFragment { username.getText().toString(), password.getText().toString()); + source.login(username.getText().toString(), password.getText().toString()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + Timber.e("Result is " + result); + }); + + super.onDialogClosed(true); } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SourceFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SourceFragment.java index 96643519f1..3d661fe100 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SourceFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/SourceFragment.java @@ -56,8 +56,12 @@ public class SourceFragment extends BaseRxFragment { public void onSourceClick(int position) { Source source = adapter.getItem(position); - CatalogueFragment fragment = CatalogueFragment.newInstance(source.getSourceId()); - activity.setFragment(fragment); + if (getPresenter().isValidSource(source)) { + CatalogueFragment fragment = CatalogueFragment.newInstance(source.getSourceId()); + activity.setFragment(fragment); + } else { + // TODO ask for password + } } private void createAdapter() { diff --git a/app/src/main/res/layout/pref_account_login.xml b/app/src/main/res/layout/pref_account_login.xml index 9409d80eff..6dc7f8a70a 100644 --- a/app/src/main/res/layout/pref_account_login.xml +++ b/app/src/main/res/layout/pref_account_login.xml @@ -50,4 +50,12 @@ android:id="@+id/show_password" android:layout_marginTop="10dp"/> +