Add ability to manually refresh google captcha

dev
k1rakishou 6 years ago
parent 6fdc7b74c2
commit 2e10056546
  1. 13
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  2. 2
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  3. 47
      Clover/app/src/main/java/org/floens/chan/ui/captcha/v2/CaptchaNoJsLayoutV2.java
  4. 293
      Clover/app/src/main/java/org/floens/chan/ui/captcha/v2/CaptchaNoJsPresenterV2.java
  5. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/BehaviourSettingsController.java
  6. 35
      Clover/app/src/main/res/layout/layout_captcha_nojs_v2.xml
  7. 3
      Clover/app/src/main/res/values/strings.xml

@ -37,6 +37,7 @@ import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse; import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.ui.captcha.AuthenticationLayoutCallback; import org.floens.chan.ui.captcha.AuthenticationLayoutCallback;
import org.floens.chan.ui.captcha.AuthenticationLayoutInterface; import org.floens.chan.ui.captcha.AuthenticationLayoutInterface;
import org.floens.chan.ui.captcha.v2.CaptchaNoJsLayoutV2;
import org.floens.chan.ui.helper.ImagePickDelegate; import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
@ -280,8 +281,13 @@ public class ReplyPresenter implements AuthenticationLayoutCallback, ImagePickDe
draft.captchaChallenge = challenge; draft.captchaChallenge = challenge;
draft.captchaResponse = response; draft.captchaResponse = response;
// should this be called here? // we don't need this to be called for new captcha window.
authenticationLayout.reset(); // Otherwise "Request captcha request is already in progress" message will be shown
if (!(authenticationLayout instanceof CaptchaNoJsLayoutV2)) {
// should this be called here?
authenticationLayout.reset();
}
makeSubmitCall(); makeSubmitCall();
} }
@ -405,8 +411,7 @@ public class ReplyPresenter implements AuthenticationLayoutCallback, ImagePickDe
} }
public void switchPage(Page page, boolean animate) { public void switchPage(Page page, boolean animate) {
// by default try to use the new nojs captcha view switchPage(page, animate, ChanSettings.useNewCaptchaWindow.get());
switchPage(page, animate, true);
} }
public void switchPage(Page page, boolean animate, boolean useV2NoJsCaptcha) { public void switchPage(Page page, boolean animate, boolean useV2NoJsCaptcha) {

@ -162,6 +162,7 @@ public class ChanSettings {
public static final BooleanSetting crashReporting; public static final BooleanSetting crashReporting;
public static final BooleanSetting useNewCaptchaWindow; public static final BooleanSetting useNewCaptchaWindow;
public static final BooleanSetting useRealGoogleCookies; public static final BooleanSetting useRealGoogleCookies;
public static final StringSetting googleCookie;
static { static {
SettingProvider p = new SharedPreferencesSettingProvider(AndroidUtils.getPreferences()); SettingProvider p = new SharedPreferencesSettingProvider(AndroidUtils.getPreferences());
@ -254,6 +255,7 @@ public class ChanSettings {
crashReporting = new BooleanSetting(p, "preference_crash_reporting", true); crashReporting = new BooleanSetting(p, "preference_crash_reporting", true);
useNewCaptchaWindow = new BooleanSetting(p, "use_new_captcha_window", true); useNewCaptchaWindow = new BooleanSetting(p, "use_new_captcha_window", true);
useRealGoogleCookies = new BooleanSetting(p, "use_real_google_cookies", false); useRealGoogleCookies = new BooleanSetting(p, "use_real_google_cookies", false);
googleCookie = new StringSetting(p, "google_cookie", "");
// Old (but possibly still in some users phone) // Old (but possibly still in some users phone)
// preference_board_view_mode default "list" // preference_board_view_mode default "list"

@ -36,6 +36,7 @@ import android.widget.ScrollView;
import android.widget.Toast; import android.widget.Toast;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication; import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.ui.captcha.AuthenticationLayoutCallback; import org.floens.chan.ui.captcha.AuthenticationLayoutCallback;
@ -55,6 +56,7 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
private AppCompatButton captchaVerifyButton; private AppCompatButton captchaVerifyButton;
private AppCompatButton useOldCaptchaButton; private AppCompatButton useOldCaptchaButton;
private AppCompatButton reloadCaptchaButton; private AppCompatButton reloadCaptchaButton;
private AppCompatButton refreshCookiesButton;
private ConstraintLayout buttonsHolder; private ConstraintLayout buttonsHolder;
private ScrollView background; private ScrollView background;
@ -90,6 +92,7 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
captchaVerifyButton = view.findViewById(R.id.captcha_layout_v2_verify_button); captchaVerifyButton = view.findViewById(R.id.captcha_layout_v2_verify_button);
useOldCaptchaButton = view.findViewById(R.id.captcha_layout_v2_use_old_captcha_button); useOldCaptchaButton = view.findViewById(R.id.captcha_layout_v2_use_old_captcha_button);
reloadCaptchaButton = view.findViewById(R.id.captcha_layout_v2_reload_button); reloadCaptchaButton = view.findViewById(R.id.captcha_layout_v2_reload_button);
refreshCookiesButton = view.findViewById(R.id.captcha_layout_v2_refresh_cookies);
buttonsHolder = view.findViewById(R.id.captcha_layout_v2_buttons_holder); buttonsHolder = view.findViewById(R.id.captcha_layout_v2_buttons_holder);
background = view.findViewById(R.id.captcha_layout_v2_background); background = view.findViewById(R.id.captcha_layout_v2_background);
@ -100,11 +103,17 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
captchaVerifyButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); captchaVerifyButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary));
useOldCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); useOldCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary));
reloadCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary)); reloadCaptchaButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary));
refreshCookiesButton.setTextColor(AndroidUtils.getAttrColor(getContext(), R.attr.text_color_primary));
captchaVerifyButton.setOnClickListener(this); captchaVerifyButton.setOnClickListener(this);
useOldCaptchaButton.setOnClickListener(this); useOldCaptchaButton.setOnClickListener(this);
reloadCaptchaButton.setOnClickListener(this); reloadCaptchaButton.setOnClickListener(this);
if (ChanSettings.useRealGoogleCookies.get()) {
refreshCookiesButton.setVisibility(View.VISIBLE);
refreshCookiesButton.setOnClickListener(this);
}
captchaVerifyButton.setEnabled(false); captchaVerifyButton.setEnabled(false);
} }
@ -148,6 +157,9 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
showToast(getContext().getString( showToast(getContext().getString(
R.string.captcha_layout_v2_captcha_request_is_already_in_progress)); R.string.captcha_layout_v2_captcha_request_is_already_in_progress));
break; break;
case AlreadyShutdown:
// do nothing
break;
} }
} }
@ -171,10 +183,35 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
}); });
} }
@Override
public void onGoogleCookiesRefreshed() {
// called on a background thread
AndroidUtils.runOnUiThread(() -> {
showToast("Google cookies successfully refreshed");
// refresh the captcha as well
reset();
});
}
// Called when we could not get google cookies
@Override
public void onGetGoogleCookieError(boolean shouldFallback, Throwable error) {
// called on a background thread
handleError(shouldFallback, error);
}
// Called when we got response from re-captcha but could not parse some part of it
@Override @Override
public void onCaptchaInfoParseError(Throwable error) { public void onCaptchaInfoParseError(Throwable error) {
// called on a background thread // called on a background thread
handleError(true, error);
}
private void handleError(boolean shouldFallback, Throwable error) {
AndroidUtils.runOnUiThread(() -> { AndroidUtils.runOnUiThread(() -> {
Logger.e(TAG, "CaptchaV2 error", error); Logger.e(TAG, "CaptchaV2 error", error);
@ -182,7 +219,10 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
showToast(message); showToast(message);
captchaVerifyButton.setEnabled(true); captchaVerifyButton.setEnabled(true);
callback.onFallbackToV1CaptchaView();
if (shouldFallback) {
callback.onFallbackToV1CaptchaView();
}
}); });
} }
@ -200,6 +240,8 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
callback.onFallbackToV1CaptchaView(); callback.onFallbackToV1CaptchaView();
} else if (v == reloadCaptchaButton) { } else if (v == reloadCaptchaButton) {
reset(); reset();
} else if (v == refreshCookiesButton) {
presenter.refreshCookies();
} }
} }
@ -269,6 +311,9 @@ public class CaptchaNoJsLayoutV2 extends FrameLayout
case AlreadyInProgress: case AlreadyInProgress:
showToast(getContext().getString(R.string.captcha_layout_v2_verification_already_in_progress)); showToast(getContext().getString(R.string.captcha_layout_v2_verification_already_in_progress));
break; break;
case AlreadyShutdown:
// do nothing
break;
} }
} catch (Throwable error) { } catch (Throwable error) {
onCaptchaInfoParseError(error); onCaptchaInfoParseError(error);

@ -18,8 +18,11 @@
package org.floens.chan.ui.captcha.v2; package org.floens.chan.ui.captcha.v2;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.utils.BackgroundUtils;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
import java.io.IOException; import java.io.IOException;
@ -30,8 +33,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import okhttp3.Call; import okhttp3.Headers;
import okhttp3.Callback;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -42,21 +44,23 @@ import okhttp3.ResponseBody;
public class CaptchaNoJsPresenterV2 { public class CaptchaNoJsPresenterV2 {
private static final String TAG = "CaptchaNoJsPresenterV2"; private static final String TAG = "CaptchaNoJsPresenterV2";
// TODO: change useragent? private static final String userAgentHeader = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36";
private static final String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"; private static final String acceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3";
private static final String acceptEncodingHeader = "deflate, br";
private static final String acceptLanguageHeader = "en-US";
private static final String recaptchaUrlBase = "https://www.google.com/recaptcha/api/fallback?k="; private static final String recaptchaUrlBase = "https://www.google.com/recaptcha/api/fallback?k=";
private static final String googleBaseUrl = "https://www.google.com/";
private static final String encoding = "UTF-8"; private static final String encoding = "UTF-8";
private static final String mediaType = "application/x-www-form-urlencoded"; private static final String mediaType = "application/x-www-form-urlencoded";
private static final String recaptchaChallengeString = "reCAPTCHA challenge"; private static final String recaptchaChallengeString = "reCAPTCHA challenge";
private static final String verificationTokenString = "fbc-verification-token"; private static final String verificationTokenString = "fbc-verification-token";
private static final String setCookieHeaderName = "set-cookie";
private static final int SUCCESS_STATUS_CODE = 200; private static final int SUCCESS_STATUS_CODE = 200;
private static final long CAPTCHA_REQUEST_THROTTLE_MS = 3000L; private static final long CAPTCHA_REQUEST_THROTTLE_MS = 3000L;
private static final String googleCookies = // this cookie is taken from dashchan
"SID=gjaHjfFJPAN5HO3MVVZpjHFKa_249dsfjHa9klsiaflsd99.asHqjsM2lAS; " + private static final String defaultGoogleCookies = "NID=87=gkOAkg09AKnvJosKq82kgnDnHj8Om2pLskKhdna02msog8HkdHDlasDf";
"HSID=j7m0aFJ82lPF7Hd9d; " +
"SSID=nJKpa81jOskq7Jsps; " +
"NID=87=gkOAkg09AKnvJosKq82kgnDnHj8Om2pLskKhdna02msog8HkdHDlasDf";
// TODO: inject this in the future when https://github.com/Floens/Clover/pull/678 is merged // TODO: inject this in the future when https://github.com/Floens/Clover/pull/678 is merged
private final OkHttpClient okHttpClient = new OkHttpClient(); private final OkHttpClient okHttpClient = new OkHttpClient();
@ -68,16 +72,22 @@ public class CaptchaNoJsPresenterV2 {
private AuthenticationCallbacks callbacks; private AuthenticationCallbacks callbacks;
@Nullable @Nullable
private CaptchaInfo prevCaptchaInfo = null; private CaptchaInfo prevCaptchaInfo = null;
@NonNull
// either the default cookie or a real cookie
private volatile String googleCookie;
private AtomicBoolean verificationInProgress = new AtomicBoolean(false); private AtomicBoolean verificationInProgress = new AtomicBoolean(false);
private AtomicBoolean captchaRequestInProgress = new AtomicBoolean(false); private AtomicBoolean captchaRequestInProgress = new AtomicBoolean(false);
private AtomicBoolean refreshCookiesRequestInProgress = new AtomicBoolean(false);
private String siteKey; private String siteKey;
private String baseUrl; private String baseUrl;
private long lastTimeCaptchRequest = 0L; private long lastTimeCaptchaRequest = 0L;
public CaptchaNoJsPresenterV2(@Nullable AuthenticationCallbacks callbacks, Context context) { public CaptchaNoJsPresenterV2(@Nullable AuthenticationCallbacks callbacks, Context context) {
this.callbacks = callbacks; this.callbacks = callbacks;
this.parser = new CaptchaNoJsHtmlParser(context, okHttpClient); this.parser = new CaptchaNoJsHtmlParser(context, okHttpClient);
this.googleCookie = ChanSettings.googleCookie.get();
} }
public void init(String siteKey, String baseUrl) { public void init(String siteKey, String baseUrl) {
@ -86,15 +96,22 @@ public class CaptchaNoJsPresenterV2 {
} }
/** /**
* Send challenge solution back * Send challenge solution back to the recaptcha
*/ */
public VerifyError verify( public VerifyError verify(
List<Integer> selectedIds List<Integer> selectedIds
) throws CaptchaNoJsV2Error, UnsupportedEncodingException { ) throws CaptchaNoJsV2Error {
if (!verificationInProgress.compareAndSet(false, true)) { if (!verificationInProgress.compareAndSet(false, true)) {
Logger.d(TAG, "Verify captcha request is already in progress");
return VerifyError.AlreadyInProgress; return VerifyError.AlreadyInProgress;
} }
if (executor.isShutdown()) {
verificationInProgress.set(false);
Logger.d(TAG, "Cannot verify, executor is already shutdown");
return VerifyError.AlreadyShutdown;
}
try { try {
if (selectedIds.isEmpty()) { if (selectedIds.isEmpty()) {
verificationInProgress.set(false); verificationInProgress.set(false);
@ -109,44 +126,41 @@ public class CaptchaNoJsPresenterV2 {
throw new CaptchaNoJsV2Error("C parameter is null"); throw new CaptchaNoJsV2Error("C parameter is null");
} }
String recaptchaUrl = recaptchaUrlBase + siteKey; if (googleCookie.isEmpty()) {
RequestBody body = createResponseBody(prevCaptchaInfo, selectedIds); throw new IllegalStateException("Google cookies are not supposed to be empty here");
}
Request request = new Request.Builder()
.url(recaptchaUrl) executor.submit(() -> {
.post(body) try {
.header("User-Agent", userAgent) String recaptchaUrl = recaptchaUrlBase + siteKey;
.header("Referer", recaptchaUrl) RequestBody body = createResponseBody(prevCaptchaInfo, selectedIds);
.header("Accept-Language", "en-US")
.header("Cookie", googleCookies) Request request = new Request.Builder()
.build(); .url(recaptchaUrl)
.post(body)
okHttpClient.newCall(request).enqueue(new Callback() { .header("Referer", recaptchaUrl)
@Override .header("User-Agent", userAgentHeader)
public void onFailure(Call call, IOException e) { .header("Accept", acceptHeader)
.header("Accept-Encoding", acceptEncodingHeader)
.header("Accept-Language", acceptLanguageHeader)
.header("Cookie", googleCookie)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
prevCaptchaInfo = handleGetRecaptchaResponse(response);
} finally {
verificationInProgress.set(false);
}
} catch (Throwable error) {
if (callbacks != null) { if (callbacks != null) {
try { try {
prevCaptchaInfo = null; prevCaptchaInfo = null;
callbacks.onCaptchaInfoParseError(e); callbacks.onCaptchaInfoParseError(error);
} finally { } finally {
verificationInProgress.set(false); verificationInProgress.set(false);
} }
} }
} }
@Override
public void onResponse(Call call, Response response) {
executor.execute(() -> {
// to avoid okhttp's threads to hang
try {
prevCaptchaInfo = handleGetRecaptchaResponse(response);
} finally {
verificationInProgress.set(false);
response.close();
}
});
}
}); });
return VerifyError.Ok; return VerifyError.Ok;
@ -156,57 +170,89 @@ public class CaptchaNoJsPresenterV2 {
} }
} }
/**
* Manually refreshes the google cookie
* */
public void refreshCookies() {
if (!refreshCookiesRequestInProgress.compareAndSet(false, true)) {
return;
}
if (executor.isShutdown()) {
refreshCookiesRequestInProgress.set(false);
return;
}
executor.submit(() -> {
try {
googleCookie = getGoogleCookies(true);
if (callbacks != null) {
callbacks.onGoogleCookiesRefreshed();
}
} catch (IOException e) {
if (callbacks != null) {
callbacks.onGetGoogleCookieError(false, e);
}
} finally {
refreshCookiesRequestInProgress.set(false);
}
});
}
/** /**
* Requests captcha data, parses it and then passes it to the render function * Requests captcha data, parses it and then passes it to the render function
*/ */
public RequestCaptchaInfoError requestCaptchaInfo() { public RequestCaptchaInfoError requestCaptchaInfo() {
if (!captchaRequestInProgress.compareAndSet(false, true)) { if (!captchaRequestInProgress.compareAndSet(false, true)) {
Logger.d(TAG, "Request captcha request is already in progress");
return RequestCaptchaInfoError.AlreadyInProgress; return RequestCaptchaInfoError.AlreadyInProgress;
} }
try { try {
// recaptcha may become very angry at you if your are fetching it too fast // recaptcha may become very angry at you if your are fetching it too fast
if (System.currentTimeMillis() - lastTimeCaptchRequest < CAPTCHA_REQUEST_THROTTLE_MS) { if (System.currentTimeMillis() - lastTimeCaptchaRequest < CAPTCHA_REQUEST_THROTTLE_MS) {
captchaRequestInProgress.set(false); captchaRequestInProgress.set(false);
Logger.d(TAG, "Requesting captcha info too fast");
return RequestCaptchaInfoError.HoldYourHorses; return RequestCaptchaInfoError.HoldYourHorses;
} }
lastTimeCaptchRequest = System.currentTimeMillis(); if (executor.isShutdown()) {
String recaptchaUrl = recaptchaUrlBase + siteKey; captchaRequestInProgress.set(false);
Logger.d(TAG, "Cannot request captcha info, executor is already shutdown");
return RequestCaptchaInfoError.AlreadyShutdown;
}
Request request = new Request.Builder() lastTimeCaptchaRequest = System.currentTimeMillis();
.url(recaptchaUrl)
.header("User-Agent", userAgent)
.header("Referer", baseUrl)
.header("Accept-Language", "en-US")
.header("Cookie", googleCookies)
.build();
okHttpClient.newCall(request).enqueue(new Callback() { executor.submit(() -> {
@Override try {
public void onFailure(Call call, IOException e) { try {
if (callbacks != null) { googleCookie = getGoogleCookies(false);
try { } catch (Throwable error) {
prevCaptchaInfo = null; if (callbacks != null) {
callbacks.onCaptchaInfoParseError(e); callbacks.onGetGoogleCookieError(true, error);
} finally {
captchaRequestInProgress.set(false);
} }
}
}
@Override throw error;
public void onResponse(Call call, Response response) { }
executor.execute(() -> {
// to avoid okhttp's threads to hang
try { try {
prevCaptchaInfo = handleGetRecaptchaResponse(response); prevCaptchaInfo = getCaptchaInfo();
} finally { } catch (Throwable error) {
captchaRequestInProgress.set(false); if (callbacks != null) {
response.close(); callbacks.onCaptchaInfoParseError(error);
} }
});
throw error;
}
} catch (Throwable error) {
Logger.e(TAG, "Error while executing captcha requests", error);
prevCaptchaInfo = null;
googleCookie = defaultGoogleCookies;
} finally {
captchaRequestInProgress.set(false);
} }
}); });
@ -223,6 +269,66 @@ public class CaptchaNoJsPresenterV2 {
} }
} }
@NonNull
private String getGoogleCookies(boolean forced) throws IOException {
if (BackgroundUtils.isMainThread()) {
throw new RuntimeException("Must not be executed on the main thread");
}
if (!ChanSettings.useRealGoogleCookies.get()) {
Logger.d(TAG, "Google cookies request is disabled in the settings, using the default ones");
return defaultGoogleCookies;
}
if (!forced && !googleCookie.isEmpty()) {
Logger.d(TAG, "We already have google cookies");
return googleCookie;
}
Request request = new Request.Builder()
.url(googleBaseUrl)
.header("User-Agent", userAgentHeader)
.header("Accept", acceptHeader)
.header("Accept-Encoding", acceptEncodingHeader)
.header("Accept-Language", acceptLanguageHeader)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
String newCookie = handleGetGoogleCookiesResponse(response);
ChanSettings.googleCookie.set(newCookie);
Logger.d(TAG, "Successfully refreshed google cookies, new cookie = " + newCookie);
return newCookie;
}
}
@Nullable
private CaptchaInfo getCaptchaInfo() throws IOException {
if (BackgroundUtils.isMainThread()) {
throw new RuntimeException("Must not be executed on the main thread");
}
if (googleCookie.isEmpty()) {
throw new IllegalStateException("Google cookies are not supposed to be null here");
}
String recaptchaUrl = recaptchaUrlBase + siteKey;
Request request = new Request.Builder()
.url(recaptchaUrl)
.header("Referer", baseUrl)
.header("User-Agent", userAgentHeader)
.header("Accept", acceptHeader)
.header("Accept-Encoding", acceptEncodingHeader)
.header("Accept-Language", acceptLanguageHeader)
.header("Cookie", googleCookie)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
return handleGetRecaptchaResponse(response);
}
}
private RequestBody createResponseBody( private RequestBody createResponseBody(
CaptchaInfo prevCaptchaInfo, CaptchaInfo prevCaptchaInfo,
List<Integer> selectedIds List<Integer> selectedIds
@ -254,6 +360,33 @@ public class CaptchaNoJsPresenterV2 {
resultBody); resultBody);
} }
@NonNull
private String handleGetGoogleCookiesResponse(Response response) {
if (response.code() != SUCCESS_STATUS_CODE) {
Logger.w(TAG, "Get google cookies request returned bad status code = " + response.code());
return defaultGoogleCookies;
}
Headers headers = response.headers();
for (String headerName : headers.names()) {
if (headerName.equalsIgnoreCase(setCookieHeaderName)) {
String setCookieHeader = headers.get(headerName);
if (setCookieHeader != null) {
String[] split = setCookieHeader.split(";");
for (String splitPart : split) {
if (splitPart.startsWith("NID")) {
return splitPart;
}
}
}
}
}
Logger.d(TAG, "Could not find the NID cookie in the headers");
return defaultGoogleCookies;
}
@Nullable @Nullable
private CaptchaInfo handleGetRecaptchaResponse(Response response) { private CaptchaInfo handleGetRecaptchaResponse(Response response) {
try { try {
@ -284,6 +417,7 @@ public class CaptchaNoJsPresenterV2 {
if (bodyString.contains(verificationTokenString)) { if (bodyString.contains(verificationTokenString)) {
// got the token // got the token
String verificationToken = parser.parseVerificationToken(bodyString); String verificationToken = parser.parseVerificationToken(bodyString);
Logger.d(TAG, "Got the verification token");
if (callbacks != null) { if (callbacks != null) {
callbacks.onVerificationDone(verificationToken); callbacks.onVerificationDone(verificationToken);
@ -293,6 +427,7 @@ public class CaptchaNoJsPresenterV2 {
} else { } else {
// got the challenge // got the challenge
CaptchaInfo captchaInfo = parser.parseHtml(bodyString, siteKey); CaptchaInfo captchaInfo = parser.parseHtml(bodyString, siteKey);
Logger.d(TAG, "Got new challenge");
if (callbacks != null) { if (callbacks != null) {
callbacks.onCaptchaInfoParsed(captchaInfo); callbacks.onCaptchaInfoParsed(captchaInfo);
@ -327,16 +462,22 @@ public class CaptchaNoJsPresenterV2 {
public enum VerifyError { public enum VerifyError {
Ok, Ok,
NoImagesSelected, NoImagesSelected,
AlreadyInProgress AlreadyInProgress,
AlreadyShutdown
} }
public enum RequestCaptchaInfoError { public enum RequestCaptchaInfoError {
Ok, Ok,
AlreadyInProgress, AlreadyInProgress,
HoldYourHorses HoldYourHorses,
AlreadyShutdown
} }
public interface AuthenticationCallbacks { public interface AuthenticationCallbacks {
void onGetGoogleCookieError(boolean shouldFallback, Throwable error);
void onGoogleCookiesRefreshed();
void onCaptchaInfoParsed(CaptchaInfo captchaInfo); void onCaptchaInfoParsed(CaptchaInfo captchaInfo);
void onCaptchaInfoParseError(Throwable error); void onCaptchaInfoParseError(Throwable error);

@ -64,6 +64,9 @@ public class BehaviourSettingsController extends SettingsController {
// when user disables the new captcha window also disable the usage of the google cookies // when user disables the new captcha window also disable the usage of the google cookies
if (!ChanSettings.useNewCaptchaWindow.get()) { if (!ChanSettings.useNewCaptchaWindow.get()) {
ChanSettings.useRealGoogleCookies.set(false); ChanSettings.useRealGoogleCookies.set(false);
// reset the old google cookie as well
ChanSettings.googleCookie.set("");
} }
rebuildPreferences(); rebuildPreferences();

@ -47,26 +47,38 @@
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/captcha_layout_v2_use_old_captcha_button" android:id="@+id/captcha_layout_v2_use_old_captcha_button"
android:layout_width="128dp" android:layout_width="112dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingEnd="16dp"
android:paddingRight="16dp"
android:text="@string/captcha_layout_v2_use_old_captcha" android:text="@string/captcha_layout_v2_use_old_captcha"
android:textColor="#ffffff" android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_reload_button" app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button"
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_reload_button" app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_refresh_cookies"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_reload_button" /> app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/captcha_layout_v2_refresh_cookies"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:text="@string/captcha_layout_v2_refresh_cookies"
android:textColor="#ffffff"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button"
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_reload_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_use_old_captcha_button"
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" />
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/captcha_layout_v2_reload_button" android:id="@+id/captcha_layout_v2_reload_button"
android:layout_width="96dp" android:layout_width="64dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
@ -76,12 +88,12 @@
app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button" app:layout_constraintBottom_toBottomOf="@+id/captcha_layout_v2_verify_button"
app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_verify_button" app:layout_constraintEnd_toStartOf="@+id/captcha_layout_v2_verify_button"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_use_old_captcha_button" app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_refresh_cookies"
app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" /> app:layout_constraintTop_toTopOf="@+id/captcha_layout_v2_verify_button" />
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/captcha_layout_v2_verify_button" android:id="@+id/captcha_layout_v2_verify_button"
android:layout_width="96dp" android:layout_width="80dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
@ -90,6 +102,7 @@
android:textColor="#ffffff" android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_reload_button" app:layout_constraintStart_toEndOf="@+id/captcha_layout_v2_reload_button"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

@ -582,7 +582,7 @@ Don't have a 4chan Pass?<br>
<string name="settings_captcha_group" tools:ignore="MissingTranslation">Captcha</string> <string name="settings_captcha_group" tools:ignore="MissingTranslation">Captcha</string>
<string name="settings_use_new_captcha_window" tools:ignore="MissingTranslation">Use new captcha window for no-js captcha</string> <string name="settings_use_new_captcha_window" tools:ignore="MissingTranslation">Use new captcha window for no-js captcha</string>
<string name="settings_use_real_google_cookies" tools:ignore="MissingTranslation">Use real google cookies instead of hardcoded ones</string> <string name="settings_use_real_google_cookies" tools:ignore="MissingTranslation">Use real google cookies instead of hardcoded ones</string>
<string name="settings_use_real_google_cookies_description" tools:ignore="MissingTranslation">By using the real google cookies a GET request to the google.com will be executed to get the google cookies (SID, HSID, SSID, NID) which will be stored on the device and used for captcha authentication. Why would you need them? Because the hardcoded ones sometimes will make you re-enter the captcha dozens of times. Those cookies will be updated automatically (once in a month). But it will also be possible to update them manually</string> <string name="settings_use_real_google_cookies_description" tools:ignore="MissingTranslation">When the real google cookies a GET request to the google.com will be executed to get the google cookies (NID) which will be used for captcha authentication. Why would you need them? Because the hardcoded ones sometimes will make you re-enter the captcha dozens of times. Those cookies will be updated automatically (when posting the first time after the app start). But it will also be possible to update them manually if necessary (for example, when the old ones give too many challenges in a row).</string>
<string name="captcha_layout_v2_verify_button_text" tools:ignore="MissingTranslation">Verify</string> <string name="captcha_layout_v2_verify_button_text" tools:ignore="MissingTranslation">Verify</string>
<string name="captcha_layout_v2_reload" tools:ignore="MissingTranslation">Reload</string> <string name="captcha_layout_v2_reload" tools:ignore="MissingTranslation">Reload</string>
@ -591,4 +591,5 @@ Don't have a 4chan Pass?<br>
<string name="captcha_layout_v2_verification_already_in_progress" tools:ignore="MissingTranslation">Verification is already in progress</string> <string name="captcha_layout_v2_verification_already_in_progress" tools:ignore="MissingTranslation">Verification is already in progress</string>
<string name="captcha_layout_v2_captcha_request_is_already_in_progress" tools:ignore="MissingTranslation">Captcha request is already in progress</string> <string name="captcha_layout_v2_captcha_request_is_already_in_progress" tools:ignore="MissingTranslation">Captcha request is already in progress</string>
<string name="captcha_layout_v2_you_are_requesting_captcha_too_fast" tools:ignore="MissingTranslation">You are requesting captcha too fast</string> <string name="captcha_layout_v2_you_are_requesting_captcha_too_fast" tools:ignore="MissingTranslation">You are requesting captcha too fast</string>
<string name="captcha_layout_v2_refresh_cookies" tools:ignore="MissingTranslation">Refresh cookies</string>
</resources> </resources>

Loading…
Cancel
Save