Browse Source

Authentication with batoto done

inorichi 9 years ago
parent
commit
1b45ff3b12

+ 34 - 3
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<Response> getResponse(final String url, final CacheControl cacheControl, final Headers headers) {
+    public Observable<Response> 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<String> getStringResponse(final String url, final CacheControl cacheControl, final Headers headers) {
+    public Observable<String> 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<Response> 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();
+    }
+
 }

+ 7 - 5
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<Integer, Source> 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;

+ 3 - 0
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();
 
 }

+ 2 - 2
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

+ 10 - 0
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<SourceFragment> {
 
     @Inject SourceManager sourceManager;
+    @Inject PreferencesHelper prefs;
 
     @Override
     protected void onTakeView(SourceFragment view) {
@@ -17,4 +20,11 @@ public class SourcePresenter extends BasePresenter<SourceFragment> {
         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(""));
+    }
 }

+ 68 - 5
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&section=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<Boolean> login(String username, String password) {
+        return mNetworkService.getStringResponse(LOGIN_URL, mRequestHeaders, null)
+                .flatMap(response -> doLogin(response, username, password))
+                .map(this::isAuthenticationSuccessful);
+    }
+
+    private Observable<Response> 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<List<Chapter>> pullChaptersFromNetwork(String mangaUrl) {
+        Observable<List<Chapter>> observable;
+        if (!isLogged()) {
+            observable = login(prefs.getSourceUsername(this), prefs.getSourcePassword(this))
+                    .flatMap(result -> super.pullChaptersFromNetwork(mangaUrl));
+        }
+        else {
+            observable = super.pullChaptersFromNetwork(mangaUrl);
+        }
+        return observable;
+    }
+
 }
 

+ 4 - 4
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

+ 15 - 0
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<Boolean> 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
 

+ 17 - 11
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<List<Manga>> 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<List<Manga>> 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<Manga> 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<List<Chapter>> 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<String> pageUrls = parseHtmlToPageUrls(unparsedHtml);
                                 return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml));
@@ -82,7 +88,7 @@ public abstract class Source extends BaseSource {
 
     private Observable<Page> 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);

+ 11 - 0
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);
         }
 

+ 6 - 2
app/src/main/java/eu/kanade/mangafeed/ui/fragment/SourceFragment.java

@@ -56,8 +56,12 @@ public class SourceFragment extends BaseRxFragment<SourcePresenter> {
     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() {

+ 8 - 0
app/src/main/res/layout/pref_account_login.xml

@@ -50,4 +50,12 @@
         android:id="@+id/show_password"
         android:layout_marginTop="10dp"/>
 
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/check_credentials"
+        android:id="@+id/check_credentials" />
+
+
+
 </LinearLayout>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -66,5 +66,6 @@
     <string name="username">Username</string>
     <string name="password">Password</string>
     <string name="show_password">Show password</string>
+    <string name="check_credentials">Check credentials</string>
 
 </resources>