From 2c7320937adc01221017c740b86c4d6851c96797 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:33:51 +0400 Subject: [PATCH] fix(YouTube - ReturnYouTubeDislike): Do not show more than 1 connection toasts if the API is broken (#560) --- .../patches/ReturnYouTubeDislikePatch.java | 27 +++--- .../ReturnYouTubeDislike.java | 10 +-- .../requests/ReturnYouTubeDislikeApi.java | 89 +++++++++++++------ 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java index 5133283f..51774f04 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import app.revanced.integrations.youtube.patches.components.ReturnYouTubeDislikeFilterPatch; import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike; +import app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.shared.PlayerType; import app.revanced.integrations.shared.Logger; @@ -21,7 +22,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote; @@ -70,17 +70,16 @@ public class ReturnYouTubeDislikePatch { private static volatile boolean lithoShortsShouldUseCurrentData; /** - * Last video id prefetched. Field is prevent prefetching the same video id multiple times in a row. + * Last video id prefetched. Field is to prevent prefetching the same video id multiple times in a row. */ @Nullable private static volatile String lastPrefetchedVideoId; public static void onRYDStatusChange(boolean rydEnabled) { - if (!rydEnabled) { - // Must remove all values to protect against using stale data - // if the user enables RYD while a video is on screen. - clearData(); - } + ReturnYouTubeDislikeApi.resetRateLimits(); + // Must remove all values to protect against using stale data + // if the user enables RYD while a video is on screen. + clearData(); } private static void clearData() { @@ -240,10 +239,6 @@ public class ReturnYouTubeDislikePatch { } replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, true, isRollingNumber); - - // When spoofing between 17.09.xx and 17.30.xx the UI is the old layout - // but uses litho and the dislikes is "|dislike_button.eml|". - // But spoofing to that range gives a broken UI layout so no point checking for that. } else if (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) { // Litho Shorts player. if (!Settings.RYD_SHORTS.get()) { @@ -300,9 +295,10 @@ public class ReturnYouTubeDislikePatch { @NonNull String original) { try { CharSequence replacement = onLithoTextLoaded(conversionContext, original, true); - if (!replacement.toString().equals(original)) { + String replacementString = replacement.toString(); + if (!replacementString.equals(original)) { rollingNumberSpan = replacement; - return replacement.toString(); + return replacementString; } // Else, the text was not a likes count but instead the view count or something else. } catch (Exception ex) { Logger.printException(() -> "onRollingNumberLoaded failure", ex); @@ -348,9 +344,8 @@ public class ReturnYouTubeDislikePatch { } else { view.setCompoundDrawables(separator, null, null, null); } - // Liking/disliking can cause the span to grow in size, - // which is ok and is laid out correctly, - // but if the user then undoes their action the layout will not remove the extra padding. + // Disliking can cause the span to grow in size, which is ok and is laid out correctly, + // but if the user then removes their dislike the layout will not adjust to the new shorter width. // Use a center alignment to take up any extra space. view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Single line mode does not clip words if the span is larger than the view bounds. diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java index 7df27578..7e7fe68a 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/ReturnYouTubeDislike.java @@ -151,7 +151,7 @@ public class ReturnYouTubeDislike { private final Future future; /** - * Time this instance and the future was created. + * Time this instance and the fetch future was created. */ private final long timeFetched; @@ -185,12 +185,12 @@ public class ReturnYouTubeDislike { /** * Color of the left and middle separator, based on the color of the right separator. - * It's unknown where YT gets the color from, and the colors here are approximated by hand. - * Ideally, the color here would be the actual color YT uses at runtime. + * It's unknown where YT gets the color from, and the values here are approximated by hand. + * Ideally, this would be the actual color YT uses at runtime. * * Older versions before the 'Me' library tab use a slightly different color. * If spoofing was previously used and is now turned off, - * or an old version was recently upgraded then the old colors are sometimes used. + * or an old version was recently upgraded then the old colors are sometimes still used. */ private static int getSeparatorColor() { if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) { @@ -411,7 +411,7 @@ public class ReturnYouTubeDislike { } /** - * Should be called if the user changes settings for dislikes appearance. + * Should be called if the user changes dislikes appearance settings. */ public static void clearAllUICaches() { synchronized (fetchCache) { diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java index 0b1c8e81..73db5f00 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java @@ -62,7 +62,7 @@ public class ReturnYouTubeDislikeApi { * How long to wait until API calls are resumed, if the API requested a back off. * No clear guideline of how long to wait until resuming. */ - private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes. + private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 10 * 60 * 1000; // 10 Minutes. /** * How long to wait until API calls are resumed, if any connection error occurs. @@ -72,7 +72,13 @@ public class ReturnYouTubeDislikeApi { /** * If non zero, then the system time of when API calls can resume. */ - private static volatile long timeToResumeAPICalls; // must be volatile, since different threads read/write to this + private static volatile long timeToResumeAPICalls; + + /** + * If the last API getVotes call failed for any reason (including server requested rate limit). + * Used to prevent showing repeat connection toasts when the API is down. + */ + private static volatile boolean lastApiCallFailed; /** * Number of times {@link #HTTP_STATUS_CODE_RATE_LIMIT} was requested by RYD api. @@ -148,6 +154,18 @@ public class ReturnYouTubeDislikeApi { } } + /** + * Clears any backoff rate limits in effect. + * Should be called if RYD is turned on/off. + */ + public static void resetRateLimits() { + if (lastApiCallFailed || timeToResumeAPICalls != 0) { + Logger.printDebug(() -> "Reset rate limit"); + } + lastApiCallFailed = false; + timeToResumeAPICalls = 0; + } + /** * @return True, if api rate limit is in effect. */ @@ -193,25 +211,36 @@ public class ReturnYouTubeDislikeApi { timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_CONNECTION_ERROR_MILLISECONDS; fetchCallResponseTimeLast = responseTimeOfFetchCall; fetchCallNumberOfFailures++; + lastApiCallFailed = true; } else if (rateLimitHit) { Logger.printDebug(() -> "API rate limit was hit. Stopping API calls for the next " + BACKOFF_RATE_LIMIT_MILLISECONDS + " seconds"); timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_RATE_LIMIT_MILLISECONDS; numberOfRateLimitRequestsEncountered++; fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT; - Utils.showToastLong(str("revanced_ryd_failure_client_rate_limit_requested")); + if (!lastApiCallFailed && Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) { + Utils.showToastLong(str("revanced_ryd_failure_client_rate_limit_requested")); + } + lastApiCallFailed = true; } else { fetchCallResponseTimeLast = responseTimeOfFetchCall; + lastApiCallFailed = false; } } - private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) { - if (Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) { - Utils.showToastShort(toastMessage); - } - if (ex != null) { - Logger.printInfo(() -> toastMessage, ex); + private static void handleConnectionError(@NonNull String toastMessage, + @Nullable Exception ex, + boolean showLongToast) { + if (!lastApiCallFailed && Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) { + if (showLongToast) { + Utils.showToastLong(toastMessage); + } else { + Utils.showToastShort(toastMessage); + } } + lastApiCallFailed = true; + + Logger.printInfo(() -> toastMessage, ex); } /** @@ -262,13 +291,15 @@ public class ReturnYouTubeDislikeApi { // fall thru to update statistics } } else { - handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null); + // Unexpected response code. Most likely RYD is temporarily broken. + handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), + null, true); } - connection.disconnect(); // something went wrong, might as well disconnect - } catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error - handleConnectionError((str("revanced_ryd_failure_connection_timeout")), ex); + connection.disconnect(); // Something went wrong, might as well disconnect. + } catch (SocketTimeoutException ex) { + handleConnectionError((str("revanced_ryd_failure_connection_timeout")), ex, false); } catch (IOException ex) { - handleConnectionError((str("revanced_ryd_failure_generic", ex.getMessage())), ex); + handleConnectionError((str("revanced_ryd_failure_generic", ex.getMessage())), ex, true); } catch (Exception ex) { // should never happen Logger.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage())); @@ -309,12 +340,13 @@ public class ReturnYouTubeDislikeApi { String solution = solvePuzzle(challenge, difficulty); return confirmRegistration(userId, solution); } - handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null); + handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), + null, true); connection.disconnect(); } catch (SocketTimeoutException ex) { - handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); + handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false); } catch (IOException ex) { - handleConnectionError(str("revanced_ryd_failure_generic", "registration failed"), ex); + handleConnectionError(str("revanced_ryd_failure_generic", "registration failed"), ex, true); } catch (Exception ex) { Logger.printException(() -> "Failed to register user", ex); // should never happen } @@ -356,12 +388,14 @@ public class ReturnYouTubeDislikeApi { final String resultLog = result == null ? "(no response)" : result; Logger.printInfo(() -> "Failed to confirm registration for user: " + userId + " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog); - handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null); + handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), + null, true); connection.disconnect(); // something went wrong, might as well disconnect } catch (SocketTimeoutException ex) { - handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); + handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false); } catch (IOException ex) { - handleConnectionError(str("revanced_ryd_failure_generic", "confirm registration failed"), ex); + handleConnectionError(str("revanced_ryd_failure_generic", "confirm registration failed"), + ex, true); } catch (Exception ex) { Logger.printException(() -> "Failed to confirm registration for user: " + userId + "solution: " + solution, ex); @@ -429,12 +463,13 @@ public class ReturnYouTubeDislikeApi { } Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote + " response code was: " + responseCode); - handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null); + handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), + null, true); connection.disconnect(); // something went wrong, might as well disconnect } catch (SocketTimeoutException ex) { - handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); + handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false); } catch (IOException ex) { - handleConnectionError(str("revanced_ryd_failure_generic", "send vote failed"), ex); + handleConnectionError(str("revanced_ryd_failure_generic", "send vote failed"), ex, true); } catch (Exception ex) { // should never happen Logger.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex); @@ -477,12 +512,14 @@ public class ReturnYouTubeDislikeApi { final String resultLog = result == null ? "(no response)" : result; Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId + " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog); - handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null); + handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), + null, true); connection.disconnect(); // something went wrong, might as well disconnect } catch (SocketTimeoutException ex) { - handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); + handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false); } catch (IOException ex) { - handleConnectionError(str("revanced_ryd_failure_generic", "confirm vote failed"), ex); + handleConnectionError(str("revanced_ryd_failure_generic", "confirm vote failed"), + ex, true); } catch (Exception ex) { Logger.printException(() -> "Failed to confirm vote for video: " + videoId + " solution: " + solution, ex); // should never happen