From 78a72ba36f7bec994dd413a28ffd4864dfce7427 Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Wed, 19 Oct 2022 16:25:56 +0200 Subject: [PATCH] Re-implement Androidacy captcha error handler --- .../java/com/fox2code/mmm/MainActivity.java | 4 + .../com/fox2code/mmm/MainApplication.java | 4 - .../com/fox2code/mmm/NotificationType.java | 14 +++ .../mmm/androidacy/AndroidacyActivity.java | 1 + .../mmm/androidacy/AndroidacyRepoData.java | 55 ++++---- .../mmm/settings/SettingsActivity.java | 34 +++-- .../java/com/fox2code/mmm/utils/Http.java | 119 ++++++++++-------- .../com/fox2code/mmm/utils/HttpException.java | 40 ++++++ .../res/drawable/ic_baseline_refresh_24.xml | 10 ++ app/src/main/res/values/strings.xml | 1 + 10 files changed, 194 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/com/fox2code/mmm/utils/HttpException.java create mode 100644 app/src/main/res/drawable/ic_baseline_refresh_24.xml diff --git a/app/src/main/java/com/fox2code/mmm/MainActivity.java b/app/src/main/java/com/fox2code/mmm/MainActivity.java index e7a3a95..147169a 100644 --- a/app/src/main/java/com/fox2code/mmm/MainActivity.java +++ b/app/src/main/java/com/fox2code/mmm/MainActivity.java @@ -211,6 +211,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe (int) (value * PRECISION), true) :() -> progressIndicator.setProgressCompat( (int) (value * PRECISION * 0.75F), true))); + NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } else { @@ -372,6 +373,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe NoodleDebug noodleDebug = NoodleDebug.getNoodleDebug(); if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); + NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); if (!NotificationType.NO_INTERNET.shouldRemove()) moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false)) @@ -433,6 +435,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe (int) (value * PRECISION), true) :() -> progressIndicator.setProgressCompat( (int) (value * PRECISION * 0.75F), true))); + NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } else { @@ -470,6 +473,7 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.progressIndicator.setVisibility(View.GONE); this.swipeRefreshLayout.setRefreshing(false); }); + NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder); if (!NotificationType.NO_INTERNET.shouldRemove()) { this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index 1dddb9b..e70319e 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -204,10 +204,6 @@ public class MainApplication extends FoxApplication private FoxThemeWrapper markwonThemeContext; private Markwon markwon; - public static MainApplication getInstance() { - return INSTANCE; - } - public Markwon getMarkwon() { if (this.markwon != null) return this.markwon; diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.java b/app/src/main/java/com/fox2code/mmm/NotificationType.java index 87583fd..999e845 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.java +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.java @@ -10,6 +10,7 @@ import androidx.annotation.StringRes; import com.fox2code.foxcompat.FoxActivity; import com.fox2code.mmm.installer.InstallerInitializer; +import com.fox2code.mmm.module.ModuleViewListBuilder; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Http; @@ -69,6 +70,15 @@ public enum NotificationType implements NotificationTypeCst { RepoManager.getINSTANCE().hasConnectivity(); } }, + NEED_CAPTCHA_ANDROIDACY(R.string.androidacy_need_captcha, R.drawable.ic_baseline_refresh_24, v -> + IntentHelper.openUrlAndroidacy(v.getContext(), + "https://" + Http.needCaptchaAndroidacyHost() + "/", false)) { + @Override + public boolean shouldRemove() { + return !RepoManager.isAndroidacyRepoEnabled() + || !Http.needCaptchaAndroidacy(); + } + }, NO_WEB_VIEW(R.string.no_web_view, R.drawable.ic_baseline_android_24) { @Override public boolean shouldRemove() { @@ -181,4 +191,8 @@ public enum NotificationType implements NotificationTypeCst { public boolean shouldRemove() { return false; } + + public final void autoAdd(ModuleViewListBuilder moduleViewListBuilder) { + if (!shouldRemove()) moduleViewListBuilder.addNotification(this); + } } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java index cca9451..6da67d6 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyActivity.java @@ -91,6 +91,7 @@ public final class AndroidacyActivity extends FoxActivity { this.forceBackPressed(); return; } + Http.markCaptchaAndroidacySolved(); if (!url.contains(AndroidacyUtil.REFERRER)) { if (url.lastIndexOf('/') < url.lastIndexOf('?')) { url = url + '&' + AndroidacyUtil.REFERRER; diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java index 509dcfb..57bccf0 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyRepoData.java @@ -2,7 +2,6 @@ package com.fox2code.mmm.androidacy; import android.content.SharedPreferences; import android.util.Log; -import android.webkit.CookieManager; import android.widget.Toast; import androidx.annotation.NonNull; @@ -14,13 +13,16 @@ import com.fox2code.mmm.repo.RepoData; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.repo.RepoModule; import com.fox2code.mmm.utils.Http; +import com.fox2code.mmm.utils.HttpException; import com.fox2code.mmm.utils.PropUtils; +import com.topjohnwu.superuser.internal.UiThreadHandler; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; @@ -73,22 +75,19 @@ public final class AndroidacyRepoData extends RepoData { return url; } - public boolean isValidToken(string token) { + public boolean isValidToken(String token) throws IOException { try { Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token, false); - } catch (Exception e) { - if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage())) { - Log.e(TAG, "We are being rate limited!", e); - long time = System.currentTimeMillis(); - this.androidacyBlockade = time + 3_600_000L; + } catch (HttpException e) { + if (e.getErrorCode() == 401) { + Log.w(TAG, "Invalid token, resetting..."); + // Remove saved preference + SharedPreferences.Editor editor = this.cachedPreferences.edit(); + editor.remove("androidacy_api_token"); + editor.apply(); return false; } - Log.w(TAG, "Invalid token, resetting..."); - // Remove saved preference - SharedPreferences.Editor editor = this.cachedPreferences.edit(); - editor.remove("androidacy_api_token"); - editor.apply(); - return false; + throw e; } // If status code is 200, we are good return true; @@ -96,6 +95,7 @@ public final class AndroidacyRepoData extends RepoData { @Override protected boolean prepare() { + if (Http.needCaptchaAndroidacy()) return false; // Implementation details discussed on telegram // First, ping the server to check if it's alive try { @@ -103,19 +103,30 @@ public final class AndroidacyRepoData extends RepoData { } catch (Exception e) { Log.e(TAG, "Failed to ping server", e); // Inform user - new Thread(() -> Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_server_down, Toast.LENGTH_LONG).show()).start(); + if (!HttpException.shouldTimeout(e)) { + UiThreadHandler.run(() -> Toast.makeText(MainApplication.getINSTANCE(), + R.string.androidacy_server_down, Toast.LENGTH_SHORT).show()); + } return false; } long time = System.currentTimeMillis(); if (this.androidacyBlockade > time) return false; this.androidacyBlockade = time + 30_000L; - if (this.token == null) { - this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null); - if (this.token != null && !this.isValidToken(this.token)) { + try { + if (this.token == null) { + this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null); + if (this.token != null && !this.isValidToken(this.token)) { + this.token = null; + } + } else if (!this.isValidToken(this.token)) { this.token = null; } - } else if (!this.isValidToken(this.token)) { - this.token = null; + } catch (IOException e) { + if (HttpException.shouldTimeout(e)) { + Log.e(TAG, "We are being rate limited!", e); + this.androidacyBlockade = time + 3_600_000L; + } + return false; } if (token == null) { try { @@ -142,7 +153,7 @@ public final class AndroidacyRepoData extends RepoData { // Save token to shared preference MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", token).apply(); } catch (Exception e) { - if ("Received error code: 419".equals(e.getMessage()) || "Received error code: 429".equals(e.getMessage()) || "Received error code: 503".equals(e.getMessage())) { + if (HttpException.shouldTimeout(e)) { Log.e(TAG, "We are being rate limited!", e); this.androidacyBlockade = time + 3_600_000L; } @@ -308,10 +319,8 @@ public final class AndroidacyRepoData extends RepoData { return this.token; } - void setToken(String token) { + public void setToken(String token) { if (Http.hasWebView()) { - // TODO: Figure out why this is needed - CookieManager.getInstance().setCookie("https://.androidacy.com/", "USER=" + token + "; expires=Fri, 31 Dec 9999 23:59:59 GMT;" + " path=/; secure; domain=.androidacy.com"); this.token = token; } } diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 0da0947..11139b6 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -462,7 +462,8 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return true; }); } - String originalApiKey = MainApplication.getSharedPreferences().getString("pref_androidacy_api_token", ""); + String[] originalApiKeyRef = new String[]{ + MainApplication.getSharedPreferences().getString("pref_androidacy_api_token", "")}; // Create the pref_androidacy_repo_api_key text input with validation EditTextPreference prefAndroidacyRepoApiKey = findPreference("pref_androidacy_repo_api_key"); assert prefAndroidacyRepoApiKey != null; @@ -477,44 +478,57 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); prefAndroidacyRepoApiKey.setPositiveButtonText(R.string.save_api_key); prefAndroidacyRepoApiKey.setOnPreferenceChangeListener((preference, newValue) -> { + if (originalApiKeyRef[0].equals(newValue)) return true; // Skip if nothing changed. // Curious if this actually works - so crash the app on purpose // throw new RuntimeException("This is a test crash"); // get original api key String apiKey = String.valueOf(newValue); // Show snack bar with indeterminate progress - Snackbar.make(requireView(), R.string.checking_api_key, Snackbar.LENGTH_INDEFINITE).setAction(R.string.cancel, v -> { + Snackbar.make(requireView(), R.string.checking_api_key, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.cancel, v -> { // Restore the original api key - prefAndroidacyRepoApiKey.setText(originalApiKey); + prefAndroidacyRepoApiKey.setText(originalApiKeyRef[0]); }).show(); // Check the API key on a background thread new Thread(() -> { // If key is empty, just remove it and change the text of the snack bar if (apiKey.isEmpty()) { - MainApplication.getSharedPreferences().edit().remove("pref_androidacy_repo_api_key").apply(); - new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), R.string.api_key_removed, Snackbar.LENGTH_SHORT).show()); + MainApplication.getSharedPreferences().edit().remove( + "pref_androidacy_repo_api_key").apply(); + new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), + R.string.api_key_removed, Snackbar.LENGTH_SHORT).show()); } else { // If key < 64 chars, it's not valid if (apiKey.length() < 64) { new Handler(Looper.getMainLooper()).post(() -> { Snackbar.make(requireView(), R.string.api_key_invalid, Snackbar.LENGTH_SHORT).show(); // Save the original key - MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", originalApiKey).apply(); + MainApplication.getSharedPreferences().edit().putString( + "pref_androidacy_api_token", originalApiKeyRef[0]).apply(); // Re-show the dialog with an error prefAndroidacyRepoApiKey.performClick(); // Show error prefAndroidacyRepoApiKey.setDialogMessage(getString(R.string.api_key_invalid)); }); } else { - boolean valid = AndroidacyRepoData.getInstance().isValidToken(apiKey); + boolean valid = false; + try { + valid = AndroidacyRepoData.getInstance().isValidToken(apiKey); + } catch (IOException ignored) {} // If the key is valid, save it if (valid) { - MainApplication.getSharedPreferences().edit().putString("pref_androidacy_repo_api_key", apiKey).apply(); - new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), R.string.api_key_valid, Snackbar.LENGTH_SHORT).show()); + originalApiKeyRef[0] = apiKey; + RepoManager.getINSTANCE().getAndroidacyRepoData().setToken(apiKey); + MainApplication.getSharedPreferences().edit().putString( + "pref_androidacy_repo_api_key", apiKey).apply(); + new Handler(Looper.getMainLooper()).post(() -> Snackbar.make(requireView(), + R.string.api_key_valid, Snackbar.LENGTH_SHORT).show()); } else { new Handler(Looper.getMainLooper()).post(() -> { Snackbar.make(requireView(), R.string.api_key_invalid, Snackbar.LENGTH_SHORT).show(); // Save the original key - MainApplication.getSharedPreferences().edit().putString("pref_androidacy_api_token", originalApiKey).apply(); + MainApplication.getSharedPreferences().edit().putString( + "pref_androidacy_api_token", originalApiKeyRef[0]).apply(); // Re-show the dialog with an error prefAndroidacyRepoApiKey.performClick(); // Show error diff --git a/app/src/main/java/com/fox2code/mmm/utils/Http.java b/app/src/main/java/com/fox2code/mmm/utils/Http.java index 0a488b7..842efc8 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Http.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Http.java @@ -1,18 +1,14 @@ package com.fox2code.mmm.utils; -import static com.fox2code.mmm.MainApplication.getInstance; - -import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Build; import android.system.ErrnoException; import android.system.Os; import android.util.Log; -import android.view.View; import android.webkit.CookieManager; import android.webkit.WebSettings; -import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -57,7 +53,6 @@ import okhttp3.dnsoverhttps.DnsOverHttps; import okio.BufferedSink; public class Http { - private static final MainApplication mainApplication = getInstance(); private static final String TAG = "Http"; private static final OkHttpClient httpClient; private static final OkHttpClient httpClientDoH; @@ -69,6 +64,7 @@ public class Http { private static final CDNCookieJar cookieJar; private static final String androidacyUA; private static final boolean hasWebView; + private static String needCaptchaAndroidacyHost; private static boolean doh; static { @@ -105,7 +101,16 @@ public class Http { httpclientBuilder.proxy(Proxy.NO_PROXY); // Do not use system proxy Dns dns = Dns.SYSTEM; try { - InetAddress[] cloudflareBootstrap = new InetAddress[]{InetAddress.getByName("162.159.36.1"), InetAddress.getByName("162.159.46.1"), InetAddress.getByName("1.1.1.1"), InetAddress.getByName("1.0.0.1"), InetAddress.getByName("162.159.132.53"), InetAddress.getByName("2606:4700:4700::1111"), InetAddress.getByName("2606:4700:4700::1001"), InetAddress.getByName("2606:4700:4700::0064"), InetAddress.getByName("2606:4700:4700::6400")}; + InetAddress[] cloudflareBootstrap = new InetAddress[]{ + InetAddress.getByName("162.159.36.1"), + InetAddress.getByName("162.159.46.1"), + InetAddress.getByName("1.1.1.1"), + InetAddress.getByName("1.0.0.1"), + InetAddress.getByName("162.159.132.53"), + InetAddress.getByName("2606:4700:4700::1111"), + InetAddress.getByName("2606:4700:4700::1001"), + InetAddress.getByName("2606:4700:4700::0064"), + InetAddress.getByName("2606:4700:4700::6400")}; dns = s -> { if ("cloudflare-dns.com".equals(s)) { return Arrays.asList(cloudflareBootstrap); @@ -114,16 +119,21 @@ public class Http { }; httpclientBuilder.dns(dns); httpclientBuilder.cookieJar(new CDNCookieJar()); - dns = new DnsOverHttps.Builder().client(httpclientBuilder.build()).url(Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query"))).bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build(); + dns = new DnsOverHttps.Builder().client(httpclientBuilder.build()).url( + Objects.requireNonNull(HttpUrl.parse("https://cloudflare-dns.com/dns-query"))) + .bootstrapDnsHosts(cloudflareBootstrap).resolvePrivateAddresses(true).build(); } catch (UnknownHostException | RuntimeException e) { Log.e(TAG, "Failed to init DoH", e); } httpclientBuilder.cookieJar(CookieJar.NO_COOKIES); // User-Agent format was agreed on telegram if (hasWebView) { - androidacyUA = WebSettings.getDefaultUserAgent(mainApplication).replace("wv", "FoxMmm" + "/" + BuildConfig.VERSION_CODE); + androidacyUA = WebSettings.getDefaultUserAgent(mainApplication) + .replace("wv", "") + "FoxMmm/" + BuildConfig.VERSION_CODE; } else { - androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + " FoxMmm/" + BuildConfig.VERSION_CODE; + androidacyUA = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; " + Build.DEVICE + ")" + + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" + + " FoxMmm/" + BuildConfig.VERSION_CODE; } httpclientBuilder.addInterceptor(chain -> { Request.Builder request = chain.request().newBuilder(); @@ -131,7 +141,8 @@ public class Http { String host = chain.request().url().host(); if (host.endsWith(".androidacy.com")) { request.header("User-Agent", androidacyUA); - } else if (!(host.equals("github.com") || host.endsWith(".github.com") || host.endsWith(".jsdelivr.net") || host.endsWith(".githubusercontent.com"))) { + } else if (!(host.equals("github.com") || host.endsWith(".github.com") || + host.endsWith(".jsdelivr.net") || host.endsWith(".githubusercontent.com"))) { if (InstallerInitializer.peekMagiskPath() != null) { request.header("User-Agent", // Declare Magisk version to the server "Magisk/" + InstallerInitializer.peekMagiskVersion()); @@ -144,7 +155,12 @@ public class Http { return chain.proceed(request.build()); }); // Fallback DNS cache responses in case request fail but already succeeded once in the past - fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com", "raw.githubusercontent.com", "camo.githubusercontent.com", "user-images.githubusercontent.com", "cdn.jsdelivr.net", "img.shields.io", "magisk-modules-repo.github.io", "www.androidacy.com", "api.androidacy.com"); + fallbackDNS = new FallBackDNS(mainApplication, dns, "github.com", "api.github.com", + "raw.githubusercontent.com", "camo.githubusercontent.com", + "user-images.githubusercontent.com", "cdn.jsdelivr.net", + "img.shields.io", "magisk-modules-repo.github.io", + "www.androidacy.com", "api.androidacy.com", + "production-api.androidacy.com"); httpclientBuilder.cookieJar(cookieJar = new CDNCookieJar(cookieManager)); httpclientBuilder.dns(Dns.SYSTEM); httpClient = followRedirects(httpclientBuilder, true).build(); @@ -177,38 +193,44 @@ public class Http { return doh ? httpClientWithCacheDoH : httpClientWithCache; } - @SuppressLint("SetJavaScriptEnabled") - public static void captchaWebview(String url) { - if (hasWebView) { - // Open the specified url in a webview - WebView webView = new WebView(mainApplication); - webView.getSettings().setJavaScriptEnabled(true); - webView.getSettings().setUserAgentString(androidacyUA); - webView.getSettings().setDomStorageEnabled(true); - webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); - // Open the url in the webview - webView.loadUrl(url); - // Show the webview - webView.setVisibility(View.VISIBLE); - } else { - // Throw an exception if the webview is not available - throw new IllegalStateException("Webview is not available"); + private static void checkNeedCaptchaAndroidacy(String url, int errorCode) { + if (errorCode == 403 && AndroidacyUtil.isAndroidacyLink(url)) { + needCaptchaAndroidacyHost = Uri.parse(url).getHost(); + } + } + + private static void checkNeedBlockAndroidacyRequest(String url) throws IOException { + if (!RepoManager.isAndroidacyRepoEnabled()) { + if (AndroidacyUtil.isAndroidacyLink(url)) { + throw new IOException("Androidacy repo is disabled, blocking url: " + url); + } + } else if (needCaptchaAndroidacy() && AndroidacyUtil.isAndroidacyLink(url)) { + throw new HttpException("Androidacy require the user to solve a captcha", 403); } } + public static boolean needCaptchaAndroidacy() { + return needCaptchaAndroidacyHost != null; + } + + public static String needCaptchaAndroidacyHost() { + return needCaptchaAndroidacyHost; + } + + public static void markCaptchaAndroidacySolved() { + needCaptchaAndroidacyHost = null; + } + @SuppressWarnings("resource") public static byte[] doHttpGet(String url, boolean allowCache) throws IOException { - if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { - throw new IOException("Androidacy repo is disabled, blocking url: " + url); - } - Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).get().build()).execute(); + checkNeedBlockAndroidacyRequest(url); + Response response = (allowCache ? getHttpClientWithCache() : getHttpClient()) + .newCall(new Request.Builder().url(url).get().build()).execute(); // 200/204 == success, 304 == cache valid - if (response.code() == 403 && AndroidacyUtil.isAndroidacyLink(url)) { - // Open webview to solve captcha - Log.e(TAG, "Received 403 error code, opening webview to solve captcha"); - captchaWebview(url); - } else if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { - throw new IOException("Received error code: " + response.code()); + if (response.code() != 200 && response.code() != 204 && + (response.code() != 304 || !allowCache)) { + checkNeedCaptchaAndroidacy(url, response.code()); + throw new HttpException(response.code()); } ResponseBody responseBody = response.body(); // Use cache api if used cached response @@ -229,20 +251,16 @@ public class Http { @SuppressWarnings("resource") private static Object doHttpPostRaw(String url, String data, boolean allowCache, boolean isRedirect) throws IOException { - if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { - throw new IOException("Androidacy repo is disabled, blocking url: " + url); - } + checkNeedBlockAndroidacyRequest(url); Response response = (isRedirect ? getHttpClientNoRedirect() : allowCache ? getHttpClientWithCache() : getHttpClient()).newCall(new Request.Builder().url(url).post(JsonRequestBody.from(data)).header("Content-Type", "application/json").build()).execute(); if (isRedirect && response.isRedirect()) { return response.request().url().uri().toString(); } // 200/204 == success, 304 == cache valid - if (response.code() == 403 && AndroidacyUtil.isAndroidacyLink(url)) { - // Open webview to solve captcha - Log.e(TAG, "Received 403 error code, opening webview to solve captcha"); - captchaWebview(url); - } else if (response.code() != 200 && response.code() != 204 && (response.code() != 304 || !allowCache)) { - throw new IOException("Received error code: " + response.code()); + if (response.code() != 200 && response.code() != 204 && + (response.code() != 304 || !allowCache)) { + checkNeedCaptchaAndroidacy(url, response.code()); + throw new HttpException(response.code()); } ResponseBody responseBody = response.body(); // Use cache api if used cached response @@ -255,12 +273,11 @@ public class Http { public static byte[] doHttpGet(String url, ProgressListener progressListener) throws IOException { Log.d("Http", "Progress URL: " + url); - if (!RepoManager.isAndroidacyRepoEnabled() && AndroidacyUtil.isAndroidacyLink(url)) { - throw new IOException("Androidacy repo is disabled, blocking url: " + url); - } + checkNeedBlockAndroidacyRequest(url); Response response = getHttpClient().newCall(new Request.Builder().url(url).get().build()).execute(); if (response.code() != 200 && response.code() != 204) { - throw new IOException("Received error code: " + response.code()); + checkNeedCaptchaAndroidacy(url, response.code()); + throw new HttpException(response.code()); } ResponseBody responseBody = Objects.requireNonNull(response.body()); InputStream inputStream = responseBody.byteStream(); diff --git a/app/src/main/java/com/fox2code/mmm/utils/HttpException.java b/app/src/main/java/com/fox2code/mmm/utils/HttpException.java new file mode 100644 index 0000000..81ed69f --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/utils/HttpException.java @@ -0,0 +1,40 @@ +package com.fox2code.mmm.utils; + +import androidx.annotation.Keep; + +import java.io.IOException; + +public final class HttpException extends IOException { + private final int errorCode; + + HttpException(String text, int errorCode) { + super(text); + this.errorCode = errorCode; + } + + @Keep + public HttpException(int errorCode) { + super("Received error code: " + errorCode); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return errorCode; + } + + public boolean shouldTimeout() { + switch (errorCode) { + case 419: + case 429: + case 503: + return true; + default: + return false; + } + } + + public static boolean shouldTimeout(Exception exception) { + return exception instanceof HttpException && + ((HttpException) exception).shouldTimeout(); + } +} diff --git a/app/src/main/res/drawable/ic_baseline_refresh_24.xml b/app/src/main/res/drawable/ic_baseline_refresh_24.xml new file mode 100644 index 0000000..b1daffb --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_refresh_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 194914b..b72f45e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -179,4 +179,5 @@ Could not retrieve token from Androidacy. Please try again later. Could not validate token for Androidacy. Please try again later. Unable to contact Androidacy server. Check your connection and try again. + Androidacy update blocked by Captcha