fix(YouTube - ReturnYouTubeDislike): Do not show more than 1 connection toasts if the API is broken (#560)

This commit is contained in:
LisoUseInAIKyrios 2024-01-29 19:33:51 +04:00 committed by GitHub
parent f1e0365487
commit 2c7320937a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 79 additions and 47 deletions

View File

@ -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.

View File

@ -151,7 +151,7 @@ public class ReturnYouTubeDislike {
private final Future<RYDVoteData> 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) {

View File

@ -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