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.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike; 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.settings.Settings;
import app.revanced.integrations.youtube.shared.PlayerType; import app.revanced.integrations.youtube.shared.PlayerType;
import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Logger;
@ -21,7 +22,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote; import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote;
@ -70,17 +70,16 @@ public class ReturnYouTubeDislikePatch {
private static volatile boolean lithoShortsShouldUseCurrentData; 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 @Nullable
private static volatile String lastPrefetchedVideoId; private static volatile String lastPrefetchedVideoId;
public static void onRYDStatusChange(boolean rydEnabled) { public static void onRYDStatusChange(boolean rydEnabled) {
if (!rydEnabled) { ReturnYouTubeDislikeApi.resetRateLimits();
// Must remove all values to protect against using stale data // Must remove all values to protect against using stale data
// if the user enables RYD while a video is on screen. // if the user enables RYD while a video is on screen.
clearData(); clearData();
}
} }
private static void clearData() { private static void clearData() {
@ -240,10 +239,6 @@ public class ReturnYouTubeDislikePatch {
} }
replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original,
true, isRollingNumber); 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|")) { } else if (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) {
// Litho Shorts player. // Litho Shorts player.
if (!Settings.RYD_SHORTS.get()) { if (!Settings.RYD_SHORTS.get()) {
@ -300,9 +295,10 @@ public class ReturnYouTubeDislikePatch {
@NonNull String original) { @NonNull String original) {
try { try {
CharSequence replacement = onLithoTextLoaded(conversionContext, original, true); CharSequence replacement = onLithoTextLoaded(conversionContext, original, true);
if (!replacement.toString().equals(original)) { String replacementString = replacement.toString();
if (!replacementString.equals(original)) {
rollingNumberSpan = replacement; rollingNumberSpan = replacement;
return replacement.toString(); return replacementString;
} // Else, the text was not a likes count but instead the view count or something else. } // Else, the text was not a likes count but instead the view count or something else.
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onRollingNumberLoaded failure", ex); Logger.printException(() -> "onRollingNumberLoaded failure", ex);
@ -348,9 +344,8 @@ public class ReturnYouTubeDislikePatch {
} else { } else {
view.setCompoundDrawables(separator, null, null, null); view.setCompoundDrawables(separator, null, null, null);
} }
// Liking/disliking can cause the span to grow in size, // Disliking can cause the span to grow in size, which is ok and is laid out correctly,
// 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.
// but if the user then undoes their action the layout will not remove the extra padding.
// Use a center alignment to take up any extra space. // Use a center alignment to take up any extra space.
view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
// Single line mode does not clip words if the span is larger than the view bounds. // 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; 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; 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. * 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. * It's unknown where YT gets the color from, and the values here are approximated by hand.
* Ideally, the color here would be the actual color YT uses at runtime. * Ideally, this would be the actual color YT uses at runtime.
* *
* Older versions before the 'Me' library tab use a slightly different color. * Older versions before the 'Me' library tab use a slightly different color.
* If spoofing was previously used and is now turned off, * 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() { private static int getSeparatorColor() {
if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) { 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() { public static void clearAllUICaches() {
synchronized (fetchCache) { 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. * 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. * 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. * 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. * 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. * 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. * @return True, if api rate limit is in effect.
*/ */
@ -193,25 +211,36 @@ public class ReturnYouTubeDislikeApi {
timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_CONNECTION_ERROR_MILLISECONDS; timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_CONNECTION_ERROR_MILLISECONDS;
fetchCallResponseTimeLast = responseTimeOfFetchCall; fetchCallResponseTimeLast = responseTimeOfFetchCall;
fetchCallNumberOfFailures++; fetchCallNumberOfFailures++;
lastApiCallFailed = true;
} else if (rateLimitHit) { } else if (rateLimitHit) {
Logger.printDebug(() -> "API rate limit was hit. Stopping API calls for the next " Logger.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
+ BACKOFF_RATE_LIMIT_MILLISECONDS + " seconds"); + BACKOFF_RATE_LIMIT_MILLISECONDS + " seconds");
timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_RATE_LIMIT_MILLISECONDS; timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_RATE_LIMIT_MILLISECONDS;
numberOfRateLimitRequestsEncountered++; numberOfRateLimitRequestsEncountered++;
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT; 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 { } else {
fetchCallResponseTimeLast = responseTimeOfFetchCall; fetchCallResponseTimeLast = responseTimeOfFetchCall;
lastApiCallFailed = false;
} }
} }
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) { private static void handleConnectionError(@NonNull String toastMessage,
if (Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) { @Nullable Exception ex,
Utils.showToastShort(toastMessage); boolean showLongToast) {
} if (!lastApiCallFailed && Settings.RYD_TOAST_ON_CONNECTION_ERROR.get()) {
if (ex != null) { if (showLongToast) {
Logger.printInfo(() -> toastMessage, ex); 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 // fall thru to update statistics
} }
} else { } 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 connection.disconnect(); // Something went wrong, might as well disconnect.
} catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error } catch (SocketTimeoutException ex) {
handleConnectionError((str("revanced_ryd_failure_connection_timeout")), ex); handleConnectionError((str("revanced_ryd_failure_connection_timeout")), ex, false);
} catch (IOException ex) { } 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) { } catch (Exception ex) {
// should never happen // should never happen
Logger.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage())); 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); String solution = solvePuzzle(challenge, difficulty);
return confirmRegistration(userId, solution); 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(); connection.disconnect();
} catch (SocketTimeoutException ex) { } catch (SocketTimeoutException ex) {
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
} catch (IOException ex) { } 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) { } catch (Exception ex) {
Logger.printException(() -> "Failed to register user", ex); // should never happen 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; final String resultLog = result == null ? "(no response)" : result;
Logger.printInfo(() -> "Failed to confirm registration for user: " + userId Logger.printInfo(() -> "Failed to confirm registration for user: " + userId
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog); + " 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 connection.disconnect(); // something went wrong, might as well disconnect
} catch (SocketTimeoutException ex) { } catch (SocketTimeoutException ex) {
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
} catch (IOException ex) { } 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) { } catch (Exception ex) {
Logger.printException(() -> "Failed to confirm registration for user: " + userId Logger.printException(() -> "Failed to confirm registration for user: " + userId
+ "solution: " + solution, ex); + "solution: " + solution, ex);
@ -429,12 +463,13 @@ public class ReturnYouTubeDislikeApi {
} }
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
+ " response code was: " + responseCode); + " 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 connection.disconnect(); // something went wrong, might as well disconnect
} catch (SocketTimeoutException ex) { } catch (SocketTimeoutException ex) {
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
} catch (IOException ex) { } 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) { } catch (Exception ex) {
// should never happen // should never happen
Logger.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex); 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; final String resultLog = result == null ? "(no response)" : result;
Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog); + " 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 connection.disconnect(); // something went wrong, might as well disconnect
} catch (SocketTimeoutException ex) { } catch (SocketTimeoutException ex) {
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex); handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
} catch (IOException ex) { } 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) { } catch (Exception ex) {
Logger.printException(() -> "Failed to confirm vote for video: " + videoId Logger.printException(() -> "Failed to confirm vote for video: " + videoId
+ " solution: " + solution, ex); // should never happen + " solution: " + solution, ex); // should never happen