From 72131387b57c5467afac982a0d6e6313c3c8ad2a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 01:47:12 -0400 Subject: [PATCH 01/10] fix: Use more accurate dislike percentage if the YT creator has hidden the likes --- .../requests/RYDVoteData.java | 109 +++++++++++++++--- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java index 820c0492f3..7ed8b2e13e 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java @@ -3,6 +3,7 @@ import static app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -23,38 +24,71 @@ public final class RYDVoteData { public final long viewCount; private final long fetchedLikeCount; - private volatile long likeCount; // read/write from different threads + private volatile long likeCount; // Read/write from different threads. + + /** + * Like count can be hidden by video creator, but RYD still tracks the number + * of like/dislikes it received thru it's browser extension and and API. + * The raw like/dislikes can be used to calculate a percentage. + * + * Raw values can be null, especially for older videos with little to no views. + */ + @Nullable + private final Long fetchedRawLikeCount; + @Nullable + private volatile Long rawLikeCount; + private volatile float likePercentage; private final long fetchedDislikeCount; - private volatile long dislikeCount; // read/write from different threads + private volatile long dislikeCount; // Read/write from different threads. + + @Nullable + private final Long fetchedRawDislikeCount; + @Nullable + private volatile Long rawDislikeCount; + private volatile float dislikePercentage; + @Nullable + private static Long getLongIfExist(JSONObject json, String key) throws JSONException { + return json.isNull(key) + ? null + : json.getLong(key); + } + /** * @throws JSONException if JSON parse error occurs, or if the values make no sense (ie: negative values) */ public RYDVoteData(@NonNull JSONObject json) throws JSONException { videoId = json.getString("id"); viewCount = json.getLong("viewCount"); + fetchedLikeCount = json.getLong("likes"); + fetchedRawLikeCount = getLongIfExist(json, "rawLikes"); + fetchedDislikeCount = json.getLong("dislikes"); + fetchedRawDislikeCount = getLongIfExist(json, "rawDislikes"); + if (viewCount < 0 || fetchedLikeCount < 0 || fetchedDislikeCount < 0) { throw new JSONException("Unexpected JSON values: " + json); } likeCount = fetchedLikeCount; dislikeCount = fetchedDislikeCount; - updatePercentages(); + + updateUsingVote(Vote.LIKE_REMOVE); // Calculate percentages. } /** - * Estimated like count + * Public like count of the video, as reported by YT when RYD last updated it's data. + * Value will always be zero if the video is hidden by the YT creator. */ public long getLikeCount() { return likeCount; } /** - * Estimated dislike count + * Estimated total dislike count, extrapolated from the public like count using RYD data. */ public long getDislikeCount() { return dislikeCount; @@ -79,28 +113,69 @@ public float getDislikePercentage() { } public void updateUsingVote(Vote vote) { + final int likesToAdd, dislikesToAdd; + switch (vote) { case LIKE: - likeCount = fetchedLikeCount + 1; - dislikeCount = fetchedDislikeCount; + likesToAdd = 1; + dislikesToAdd = 0; break; case DISLIKE: - likeCount = fetchedLikeCount; - dislikeCount = fetchedDislikeCount + 1; + likesToAdd = 0; + dislikesToAdd = 1; break; case LIKE_REMOVE: - likeCount = fetchedLikeCount; - dislikeCount = fetchedDislikeCount; + likesToAdd = 0; + dislikesToAdd = 0; break; default: throw new IllegalStateException(); } - updatePercentages(); - } - private void updatePercentages() { - likePercentage = (likeCount == 0 ? 0 : (float) likeCount / (likeCount + dislikeCount)); - dislikePercentage = (dislikeCount == 0 ? 0 : (float) dislikeCount / (likeCount + dislikeCount)); + // If a video has no public likes but RYD has raw like data, + // then use the raw data instead. + Long localRawLikeCount = fetchedRawLikeCount; + Long localRawDislikeCount = fetchedRawDislikeCount; + final boolean hasRawData = localRawLikeCount != null && localRawDislikeCount != null; + final boolean videoHasNoPublicLikes = fetchedLikeCount == 0; + + likeCount = fetchedLikeCount + likesToAdd; + // RYD now always returns an estimated dislike count, even if the likes are hidden. + dislikeCount = fetchedDislikeCount + dislikesToAdd; + + if (hasRawData) { + localRawLikeCount += likesToAdd; + localRawDislikeCount += dislikesToAdd; + rawLikeCount = localRawLikeCount; + rawDislikeCount = localRawDislikeCount; + } else { + rawLikeCount = null; + rawDislikeCount = null; + } + + // Update percentages. + + if (videoHasNoPublicLikes && hasRawData) { + // Video creator has hidden the like count, + // but can use the raw like/dislikes to calculate a percentage. + final float totalRawCount = localRawLikeCount + localRawDislikeCount; + if (totalRawCount == 0) { + likePercentage = 0; + dislikePercentage = 0; + } else { + likePercentage = localRawLikeCount / totalRawCount; + dislikePercentage = localRawDislikeCount / totalRawCount; + } + } else { + final float totalCount = likeCount + dislikeCount; + if (totalCount == 0) { + likePercentage = 0; + dislikePercentage = 0; + } else { + likePercentage = likeCount / totalCount; + dislikePercentage = dislikeCount / totalCount; + } + } } @NonNull @@ -110,7 +185,9 @@ public String toString() { + "videoId=" + videoId + ", viewCount=" + viewCount + ", likeCount=" + likeCount + + ", rawLikeCount=" + rawLikeCount + ", dislikeCount=" + dislikeCount + + ", rawDislikeCount=" + rawDislikeCount + ", likePercentage=" + likePercentage + ", dislikePercentage=" + dislikePercentage + '}'; From 94024f892cadfe38a2463159dcceda2896d61014 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 02:09:11 -0400 Subject: [PATCH 02/10] fix: Show like count of regular videos --- .../ReturnYouTubeDislike.java | 13 ++++++------- .../requests/RYDVoteData.java | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) 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 bfff1b155b..ee09376b2a 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 @@ -229,17 +229,16 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // and the like count appears as a device language specific string that says 'Like'. // Check if the string contains any numbers. if (!stringContainsNumber(oldLikesString)) { - // Likes are hidden. - // RYD does not provide usable data for these types of videos, - // and the API returns bogus data (zero likes and zero dislikes) - // discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530 + // Likes are hidden by video creator + // + // RYD does not directly provide like data, but can use an estimated likes + // using the same scale factor RYD applied to the raw dislikes. // // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw // - // Change the "Likes" string to show that likes and dislikes are hidden. - String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner"); - return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString); + Logger.printDebug(() -> "Replacing hidden likes count with estimated likes: " + voteData.getLikeCount()); + oldLikesString = formatDislikeCount(voteData.getLikeCount()); } SpannableStringBuilder builder = new SpannableStringBuilder(); diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java index 7ed8b2e13e..e63715dcf5 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java @@ -81,7 +81,9 @@ public RYDVoteData(@NonNull JSONObject json) throws JSONException { /** * Public like count of the video, as reported by YT when RYD last updated it's data. - * Value will always be zero if the video is hidden by the YT creator. + * + * If the likes were hidden by the video creator, then this returns an + * estimated likes using the same extrapolation as the dislikes. */ public long getLikeCount() { return likeCount; @@ -139,7 +141,16 @@ public void updateUsingVote(Vote vote) { final boolean hasRawData = localRawLikeCount != null && localRawDislikeCount != null; final boolean videoHasNoPublicLikes = fetchedLikeCount == 0; - likeCount = fetchedLikeCount + likesToAdd; + if (videoHasNoPublicLikes && hasRawData && localRawDislikeCount > 0) { + // YT creator has hidden the likes count. + // For this situation RYD returns 0 likes and does not estimate the total likes, + // but we can do the estimate using the same data used to estimate the dislikes, + // by using the same scale factor that was applied to the raw dislikes. + final float estimatedRawScaleFactor = (float) fetchedDislikeCount / localRawDislikeCount; + likeCount = (long) (estimatedRawScaleFactor * localRawLikeCount) + likesToAdd; + } else { + likeCount = fetchedLikeCount + likesToAdd; + } // RYD now always returns an estimated dislike count, even if the likes are hidden. dislikeCount = fetchedDislikeCount + dislikesToAdd; From a519a20436f9240384a9cb8bc309ab79ee7503f7 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 02:43:32 -0400 Subject: [PATCH 03/10] fix: Show dislike numbers with the same number digits as stock YT --- .../ReturnYouTubeDislike.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) 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 ee09376b2a..1109ac80e5 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 @@ -10,6 +10,9 @@ import android.graphics.drawable.shapes.OvalShape; import android.graphics.drawable.shapes.RectShape; import android.icu.text.CompactDecimalFormat; +import android.icu.text.DecimalFormat; +import android.icu.text.DecimalFormatSymbols; +import android.icu.text.NumberFormat; import android.os.Build; import android.text.Spannable; import android.text.SpannableString; @@ -25,17 +28,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.text.NumberFormat; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Utils; @@ -353,13 +350,18 @@ private static String formatDislikeCount(long dislikeCount) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize if (dislikeCountFormatter == null) { - // Note: Java number formatters will use the locale specific number characters. - // such as Arabic which formats "1.234" into "۱,۲۳٤" - // But YouTube disregards locale specific number characters - // and instead shows english number characters everywhere. Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale; - Logger.printDebug(() -> "Locale: " + locale); dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT); + + // YouTube disregards locale specific number characters + // and instead shows english number characters everywhere. + // To use the same behavior, override the digit characters to use English + // so languages such as Arabic will show "1.234" instead of the native "۱,۲۳٤" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); + symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings()); + dislikeCountFormatter.setDecimalFormatSymbols(symbols); + } } return dislikeCountFormatter.format(dislikeCount); } @@ -373,8 +375,15 @@ private static String formatDislikePercentage(float dislikePercentage) { synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize if (dislikePercentageFormatter == null) { Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale; - Logger.printDebug(() -> "Locale: " + locale); dislikePercentageFormatter = NumberFormat.getPercentInstance(locale); + + // Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && dislikePercentageFormatter instanceof DecimalFormat) { + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); + symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings()); + ((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols); + } } if (dislikePercentage >= 0.01) { // at least 1% dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points From b97bbaee6b1cc1a15c04dd2a04f2a2dad1eae2b2 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 03:11:41 -0400 Subject: [PATCH 04/10] fix: Do not fetch RYD for Shorts if RYD is completely turned off --- .../components/ReturnYouTubeDislikeFilterPatch.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java index 927e449341..22d00f2680 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java @@ -52,7 +52,7 @@ protected boolean removeEldestEntry(Entry eldest) { @SuppressWarnings("unused") public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) { try { - if (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get()) { + if (!isShortAndOpeningOrPlaying || !Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { return; } synchronized (lastVideoIds) { @@ -68,8 +68,10 @@ public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOp private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList(); public ReturnYouTubeDislikeFilterPatch() { + // Likes always seems to load before the dislikes, but if this + // ever changes then both likes and dislikes need callbacks. addPathCallbacks( - new StringFilterGroup(Settings.RYD_SHORTS, "|shorts_dislike_button.eml|") + new StringFilterGroup(null, "|shorts_dislike_button.eml") ); // After the dislikes icon name is some binary data and then the video id for that specific short. videoIdFilterGroup.addAll( @@ -83,6 +85,10 @@ public ReturnYouTubeDislikeFilterPatch() { @Override boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { + return false; + } + FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray); if (result.isFiltered()) { String matchedVideoId = findVideoId(protobufBufferArray); From c670a0e12fbc39131c4e29c9b56345b1f82cf37f Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 04:12:34 -0400 Subject: [PATCH 05/10] refactor --- .../requests/RYDVoteData.java | 70 ++++++------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java index e63715dcf5..239ad2b095 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java +++ b/app/src/main/java/app/revanced/integrations/youtube/returnyoutubedislike/requests/RYDVoteData.java @@ -8,6 +8,8 @@ import org.json.JSONException; import org.json.JSONObject; +import app.revanced.integrations.shared.Logger; + /** * ReturnYouTubeDislike API estimated like/dislike/view counts. * @@ -25,7 +27,6 @@ public final class RYDVoteData { private final long fetchedLikeCount; private volatile long likeCount; // Read/write from different threads. - /** * Like count can be hidden by video creator, but RYD still tracks the number * of like/dislikes it received thru it's browser extension and and API. @@ -35,19 +36,12 @@ public final class RYDVoteData { */ @Nullable private final Long fetchedRawLikeCount; - @Nullable - private volatile Long rawLikeCount; - private volatile float likePercentage; private final long fetchedDislikeCount; private volatile long dislikeCount; // Read/write from different threads. - @Nullable private final Long fetchedRawDislikeCount; - @Nullable - private volatile Long rawDislikeCount; - private volatile float dislikePercentage; @Nullable @@ -136,56 +130,34 @@ public void updateUsingVote(Vote vote) { // If a video has no public likes but RYD has raw like data, // then use the raw data instead. - Long localRawLikeCount = fetchedRawLikeCount; - Long localRawDislikeCount = fetchedRawDislikeCount; - final boolean hasRawData = localRawLikeCount != null && localRawDislikeCount != null; final boolean videoHasNoPublicLikes = fetchedLikeCount == 0; - - if (videoHasNoPublicLikes && hasRawData && localRawDislikeCount > 0) { - // YT creator has hidden the likes count. - // For this situation RYD returns 0 likes and does not estimate the total likes, - // but we can do the estimate using the same data used to estimate the dislikes, - // by using the same scale factor that was applied to the raw dislikes. - final float estimatedRawScaleFactor = (float) fetchedDislikeCount / localRawDislikeCount; - likeCount = (long) (estimatedRawScaleFactor * localRawLikeCount) + likesToAdd; + final boolean hasRawData = fetchedRawLikeCount != null && fetchedRawDislikeCount != null; + + if (videoHasNoPublicLikes && hasRawData && fetchedRawDislikeCount > 0) { + // YT creator has hidden the likes count, and this is an older video that + // RYD does not provide estimated like counts. + // + // But we can calculate the public likes the same way RYD does for newer videos with hidden likes, + // by using the same raw to estimated scale factor applied to dislikes. + // This calculation exactly matches the public likes RYD provides for newer hidden videos. + final float estimatedRawScaleFactor = (float) fetchedDislikeCount / fetchedRawDislikeCount; + likeCount = (long) (estimatedRawScaleFactor * fetchedRawLikeCount) + likesToAdd; + Logger.printDebug(() -> "Using locally calculated estimated likes since RYD did not return an estimate"); } else { likeCount = fetchedLikeCount + likesToAdd; } // RYD now always returns an estimated dislike count, even if the likes are hidden. dislikeCount = fetchedDislikeCount + dislikesToAdd; - if (hasRawData) { - localRawLikeCount += likesToAdd; - localRawDislikeCount += dislikesToAdd; - rawLikeCount = localRawLikeCount; - rawDislikeCount = localRawDislikeCount; - } else { - rawLikeCount = null; - rawDislikeCount = null; - } - // Update percentages. - if (videoHasNoPublicLikes && hasRawData) { - // Video creator has hidden the like count, - // but can use the raw like/dislikes to calculate a percentage. - final float totalRawCount = localRawLikeCount + localRawDislikeCount; - if (totalRawCount == 0) { - likePercentage = 0; - dislikePercentage = 0; - } else { - likePercentage = localRawLikeCount / totalRawCount; - dislikePercentage = localRawDislikeCount / totalRawCount; - } + final float totalCount = likeCount + dislikeCount; + if (totalCount == 0) { + likePercentage = 0; + dislikePercentage = 0; } else { - final float totalCount = likeCount + dislikeCount; - if (totalCount == 0) { - likePercentage = 0; - dislikePercentage = 0; - } else { - likePercentage = likeCount / totalCount; - dislikePercentage = dislikeCount / totalCount; - } + likePercentage = likeCount / totalCount; + dislikePercentage = dislikeCount / totalCount; } } @@ -196,9 +168,7 @@ public String toString() { + "videoId=" + videoId + ", viewCount=" + viewCount + ", likeCount=" + likeCount - + ", rawLikeCount=" + rawLikeCount + ", dislikeCount=" + dislikeCount - + ", rawDislikeCount=" + rawDislikeCount + ", likePercentage=" + likePercentage + ", dislikePercentage=" + dislikePercentage + '}'; From a3056997c41cb9f6fd0110901bbc36b68983c92c Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 04:30:23 -0400 Subject: [PATCH 06/10] fix: Restore Shorts hidden likes --- .../patches/ReturnYouTubeDislikePatch.java | 82 +++++++++++-------- .../ReturnYouTubeDislikeFilterPatch.java | 13 +-- .../ReturnYouTubeDislike.java | 42 ++++++++-- 3 files changed, 92 insertions(+), 45 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 0fb8482957..d8a813e7f1 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 @@ -225,7 +225,6 @@ private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, return original; } - final CharSequence replacement; if (conversionContextString.contains("segmented_like_dislike_button.eml")) { // Regular video. ReturnYouTubeDislike videoData = currentVideoData; @@ -235,46 +234,64 @@ private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, if (!(original instanceof Spanned)) { original = new SpannableString(original); } - replacement = videoData.getDislikesSpanForRegularVideo((Spanned) original, + return videoData.getDislikesSpanForRegularVideo((Spanned) original, true, isRollingNumber); - } else if (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) { - // Litho Shorts player. - if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) { - // Must clear the current video here, otherwise if the user opens a regular video - // then opens a litho short (while keeping the regular video on screen), then closes the short, - // the original video may show the incorrect dislike value. - clearData(); - return original; - } - ReturnYouTubeDislike videoData = lastLithoShortsVideoData; - if (videoData == null) { - // The Shorts litho video id filter did not detect the video id. - // This is normal in incognito mode, but otherwise is abnormal. - Logger.printDebug(() -> "Cannot modify Shorts litho span, data is null"); - return original; - } - // Use the correct dislikes data after voting. - if (lithoShortsShouldUseCurrentData) { - lithoShortsShouldUseCurrentData = false; - videoData = currentVideoData; - if (videoData == null) { - Logger.printException(() -> "currentVideoData is null"); // Should never happen - return original; - } - Logger.printDebug(() -> "Using current video data for litho span"); - } - replacement = videoData.getDislikeSpanForShort((Spanned) original); - } else { - return original; } - return replacement; + if (isRollingNumber) { + return original; // No need to check for Shorts in the context. + } + + if (conversionContextString.contains("|shorts_like_button.eml")) { + return getShortsSpan(original, false); + } + + if (conversionContextString.contains("|shorts_dislike_button.eml")) { + return getShortsSpan(original, true); + } } catch (Exception ex) { Logger.printException(() -> "onLithoTextLoaded failure", ex); } return original; } + private static CharSequence getShortsSpan(@NonNull CharSequence original, boolean isDislikesSpan) { + // Litho Shorts player. + if (!Settings.RYD_SHORTS.get() || (isDislikesSpan && Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) + || (!isDislikesSpan && Settings.HIDE_SHORTS_LIKE_BUTTON.get())) { + // Must clear the current video here, otherwise if the user opens a regular video + // then opens a litho short (while keeping the regular video on screen), then closes the short, + // the original video may show the incorrect dislike value. + clearData(); + return original; + } + + ReturnYouTubeDislike videoData = lastLithoShortsVideoData; + if (videoData == null) { + // The Shorts litho video id filter did not detect the video id. + // This is normal in incognito mode, but otherwise is abnormal. + Logger.printDebug(() -> "Cannot modify Shorts litho span, data is null"); + return original; + } + + // Use the correct dislikes data after voting. + if (lithoShortsShouldUseCurrentData) { + if (isDislikesSpan) { + lithoShortsShouldUseCurrentData = false; + } + videoData = currentVideoData; + if (videoData == null) { + Logger.printException(() -> "currentVideoData is null"); // Should never happen + return original; + } + Logger.printDebug(() -> "Using current video data for litho span"); + } + + return isDislikesSpan + ? videoData.getDislikeSpanForShort((Spanned) original) + : videoData.getLikeSpanForShort((Spanned) original); + } + // // Rolling Number // @@ -657,6 +674,7 @@ public static void setLastLithoShortsVideoId(@Nullable String videoId) { clearData(); return; } + Logger.printDebug(() -> "New litho Shorts video id: " + videoId); ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId); videoData.setVideoIdIsShort(true); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java index 22d00f2680..11bffcc54e 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java @@ -71,14 +71,15 @@ public ReturnYouTubeDislikeFilterPatch() { // Likes always seems to load before the dislikes, but if this // ever changes then both likes and dislikes need callbacks. addPathCallbacks( - new StringFilterGroup(null, "|shorts_dislike_button.eml") + new StringFilterGroup(null, "|shorts_like_button.eml") ); - // After the dislikes icon name is some binary data and then the video id for that specific short. + + // After the likes icon name is some binary data and then the video id for that specific short. videoIdFilterGroup.addAll( - // Video was previously disliked before video was opened. - new ByteArrayFilterGroup(null, "ic_right_dislike_on_shadowed"), - // Video was not already disliked. - new ByteArrayFilterGroup(null, "ic_right_dislike_off_shadowed") + // Video was previously liked before video was opened. + new ByteArrayFilterGroup(null, "ic_right_like_on_shadowed"), + // Video was not already liked. + new ByteArrayFilterGroup(null, "ic_right_like_off_shadowed") ); } 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 1109ac80e5..9716aa8d3d 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 @@ -234,7 +234,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw // - Logger.printDebug(() -> "Replacing hidden likes count with estimated likes: " + voteData.getLikeCount()); + Logger.printDebug(() -> "Replacing hidden likes count with estimated likes"); oldLikesString = formatDislikeCount(voteData.getLikeCount()); } @@ -242,9 +242,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get(); if (!compactLayout) { - String leftSeparatorString = Utils.isRightToLeftTextLayout() - ? "\u200F" // u200F = right to left character - : "\u200E"; // u200E = left to right character + String leftSeparatorString = getTextDirectionString(); final Spannable leftSeparatorSpan; if (isRollingNumber) { leftSeparatorSpan = new SpannableString(leftSeparatorString); @@ -288,6 +286,12 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, return new SpannableString(builder); } + private static @NonNull String getTextDirectionString() { + return Utils.isRightToLeftTextLayout() + ? "\u200F" // u200F = right to left character + : "\u200E"; // u200E = left to right character + } + /** * @return If the text is likely for a previously created likes/dislikes segmented span. */ @@ -330,6 +334,10 @@ private static boolean spansHaveEqualTextAndColor(@NonNull Spanned one, @NonNull return true; } + private static SpannableString newSpannableWithLikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) { + return newSpanUsingStylingOfAnotherSpan(sourceStyling, formatDislikeCount(voteData.getLikeCount())); + } + private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) { return newSpanUsingStylingOfAnotherSpan(sourceStyling, Settings.RYD_DISLIKE_PERCENTAGE.get() @@ -492,7 +500,17 @@ public synchronized void setVideoIdIsShort(boolean isShort) { public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned original, boolean isSegmentedButton, boolean isRollingNumber) { - return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, isRollingNumber,false); + return waitForFetchAndUpdateReplacementSpan(original, isSegmentedButton, + isRollingNumber, false, false); + } + + /** + * Called when a Shorts like Spannable is created. + */ + @NonNull + public synchronized Spanned getLikeSpanForShort(@NonNull Spanned original) { + return waitForFetchAndUpdateReplacementSpan(original, false, + false, true, true); } /** @@ -500,14 +518,16 @@ public synchronized Spanned getDislikesSpanForRegularVideo(@NonNull Spanned orig */ @NonNull public synchronized Spanned getDislikeSpanForShort(@NonNull Spanned original) { - return waitForFetchAndUpdateReplacementSpan(original, false, false, true); + return waitForFetchAndUpdateReplacementSpan(original, false, + false, true, false); } @NonNull private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isSegmentedButton, boolean isRollingNumber, - boolean spanIsForShort) { + boolean spanIsForShort, + boolean spanIsForShortsLikes) { try { RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH); if (votingData == null) { @@ -534,6 +554,12 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, return original; } + if (spanIsForShortsLikes) { + // Scrolling Shorts does not cause the Spans to be reloaded, + // so there is no need to cache the likes for this situations. + return newSpannableWithLikes(original, votingData); + } + if (originalDislikeSpan != null && replacementLikeDislikeSpan != null) { if (spansHaveEqualTextAndColor(original, replacementLikeDislikeSpan)) { Logger.printDebug(() -> "Ignoring previously created dislikes span of data: " + videoId); @@ -544,6 +570,8 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, return replacementLikeDislikeSpan; } } + + // Edit: This block of code may no longer be needed. if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original.toString())) { // need to recreate using original, as original has prior outdated dislike values if (originalDislikeSpan == null) { From 97b52923c81b88f697bd7e151d827e712ba86314 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 04:56:04 -0400 Subject: [PATCH 07/10] fix compilation --- .../ReturnYouTubeDislike.java | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) 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 9716aa8d3d..915a55a95b 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 @@ -380,26 +380,31 @@ private static String formatDislikeCount(long dislikeCount) { } private static String formatDislikePercentage(float dislikePercentage) { - synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize - if (dislikePercentageFormatter == null) { - Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale; - dislikePercentageFormatter = NumberFormat.getPercentInstance(locale); - - // Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P - && dislikePercentageFormatter instanceof DecimalFormat) { - DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); - symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings()); - ((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize + if (dislikePercentageFormatter == null) { + Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale; + dislikePercentageFormatter = NumberFormat.getPercentInstance(locale); + + // Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && dislikePercentageFormatter instanceof DecimalFormat) { + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); + symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings()); + ((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols); + } } + if (dislikePercentage >= 0.01) { // at least 1% + dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points + } else { + dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision + } + return dislikePercentageFormatter.format(dislikePercentage); } - if (dislikePercentage >= 0.01) { // at least 1% - dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points - } else { - dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision - } - return dislikePercentageFormatter.format(dislikePercentage); } + + // Will never be reached, as the oldest supported YouTube app requires Android N or greater. + return String.valueOf((int) (dislikePercentage * 100)); } @NonNull From 924075640de33d5a34e7d67e9b46bb7e64d1ad94 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 15:32:19 -0400 Subject: [PATCH 08/10] refactor: Cleanup, remove unneeded code --- .../revanced/integrations/shared/Utils.java | 17 +++++ .../patches/ReturnYouTubeDislikePatch.java | 10 +-- .../ReturnYouTubeDislike.java | 63 ++++++------------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/shared/Utils.java b/app/src/main/java/app/revanced/integrations/shared/Utils.java index 4b13a7876d..21a97a9a7f 100644 --- a/app/src/main/java/app/revanced/integrations/shared/Utils.java +++ b/app/src/main/java/app/revanced/integrations/shared/Utils.java @@ -363,6 +363,23 @@ public static boolean isRightToLeftTextLayout() { return isRightToLeftTextLayout; } + /** + * @return if the text contains at least 1 number character, + * including any unicode numbers such as Arabic. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean containsNumber(@NonNull CharSequence text) { + for (int index = 0, length = text.length(); index < length;) { + final int codePoint = Character.codePointAt(text, index); + if (Character.isDigit(codePoint)) { + return true; + } + index += Character.charCount(codePoint); + } + + return false; + } + /** * Safe to call from any thread */ 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 d8a813e7f1..a43c263d7b 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 @@ -242,7 +242,10 @@ private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, return original; // No need to check for Shorts in the context. } - if (conversionContextString.contains("|shorts_like_button.eml")) { + // Check the original string first, since it's almost always shorter than the context. + if (!Utils.containsNumber(original) + && conversionContextString.contains("|shorts_like_button.eml")) { + Logger.printDebug(() -> "Replacing hidden likes count"); return getShortsSpan(original, false); } @@ -259,10 +262,6 @@ private static CharSequence getShortsSpan(@NonNull CharSequence original, boolea // Litho Shorts player. if (!Settings.RYD_SHORTS.get() || (isDislikesSpan && Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) || (!isDislikesSpan && Settings.HIDE_SHORTS_LIKE_BUTTON.get())) { - // Must clear the current video here, otherwise if the user opens a regular video - // then opens a litho short (while keeping the regular video on screen), then closes the short, - // the original video may show the incorrect dislike value. - clearData(); return original; } @@ -614,6 +613,7 @@ public static void preloadVideoId(@NonNull String videoId, boolean isShortAndOpe Logger.printDebug(() -> "Waiting for prefetch to complete: " + videoId); fetch.getFetchData(20000); // Any arbitrarily large max wait time. } + // Set the fields after the fetch completes, so any concurrent calls will also wait. lastPlayerResponseWasShort = videoIdIsShort; lastPrefetchedVideoId = videoId; 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 915a55a95b..b63d0484e0 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 @@ -220,12 +220,12 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // Note: Some locales use right to left layout (Arabic, Hebrew, etc). // If making changes to this code, change device settings to a RTL language and verify layout is correct. - String oldLikesString = oldSpannable.toString(); + CharSequence oldLikes = oldSpannable; // YouTube creators can hide the like count on a video, // and the like count appears as a device language specific string that says 'Like'. // Check if the string contains any numbers. - if (!stringContainsNumber(oldLikesString)) { + if (!Utils.containsNumber(oldLikes)) { // Likes are hidden by video creator // // RYD does not directly provide like data, but can use an estimated likes @@ -234,8 +234,8 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw // - Logger.printDebug(() -> "Replacing hidden likes count with estimated likes"); - oldLikesString = formatDislikeCount(voteData.getLikeCount()); + Logger.printDebug(() -> "Using estimated likes"); + oldLikes = formatDislikeCount(voteData.getLikeCount()); } SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -261,7 +261,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, } // likes - builder.append(newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikesString)); + builder.append(newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikes)); // middle separator String middleSeparatorString = compactLayout @@ -299,20 +299,6 @@ public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) { return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0; } - /** - * Correctly handles any unicode numbers (such as Arabic numbers). - * - * @return if the string contains at least 1 number. - */ - private static boolean stringContainsNumber(@NonNull String text) { - for (int index = 0, length = text.length(); index < length; index++) { - if (Character.isDigit(text.codePointAt(index))) { - return true; - } - } - return false; - } - private static boolean spansHaveEqualTextAndColor(@NonNull Spanned one, @NonNull Spanned two) { // Cannot use equals on the span, because many of the inner styling spans do not implement equals. // Instead, compare the underlying text and the text color to handle when dark mode is changed. @@ -346,11 +332,16 @@ private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceS } private static SpannableString newSpanUsingStylingOfAnotherSpan(@NonNull Spanned sourceStyle, @NonNull CharSequence newSpanText) { + if (sourceStyle == newSpanText && sourceStyle instanceof SpannableString) { + return (SpannableString) sourceStyle; // Nothing to do. + } + SpannableString destination = new SpannableString(newSpanText); Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class); for (Object span : spans) { destination.setSpan(span, 0, destination.length(), sourceStyle.getSpanFlags(span)); } + return destination; } @@ -532,7 +523,7 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isSegmentedButton, boolean isRollingNumber, boolean spanIsForShort, - boolean spanIsForShortsLikes) { + boolean spanIsForLikes) { try { RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH); if (votingData == null) { @@ -559,32 +550,17 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, return original; } - if (spanIsForShortsLikes) { + if (spanIsForLikes) { // Scrolling Shorts does not cause the Spans to be reloaded, // so there is no need to cache the likes for this situations. + Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId); return newSpannableWithLikes(original, votingData); } - if (originalDislikeSpan != null && replacementLikeDislikeSpan != null) { - if (spansHaveEqualTextAndColor(original, replacementLikeDislikeSpan)) { - Logger.printDebug(() -> "Ignoring previously created dislikes span of data: " + videoId); - return original; - } - if (spansHaveEqualTextAndColor(original, originalDislikeSpan)) { - Logger.printDebug(() -> "Replacing span with previously created dislike span of data: " + videoId); - return replacementLikeDislikeSpan; - } - } - - // Edit: This block of code may no longer be needed. - if (isSegmentedButton && isPreviouslyCreatedSegmentedSpan(original.toString())) { - // need to recreate using original, as original has prior outdated dislike values - if (originalDislikeSpan == null) { - // Should never happen. - Logger.printDebug(() -> "Cannot add dislikes - original span is null. videoId: " + videoId); - return original; - } - original = originalDislikeSpan; + if (originalDislikeSpan != null && replacementLikeDislikeSpan != null + && spansHaveEqualTextAndColor(original, originalDislikeSpan)) { + Logger.printDebug(() -> "Replacing span with previously created dislike span of data: " + videoId); + return replacementLikeDislikeSpan; } // No replacement span exist, create it now. @@ -599,9 +575,10 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, return replacementLikeDislikeSpan; } - } catch (Exception e) { - Logger.printException(() -> "waitForFetchAndUpdateReplacementSpan failure", e); // should never happen + } catch (Exception ex) { + Logger.printException(() -> "waitForFetchAndUpdateReplacementSpan failure", ex); } + return original; } From ae9864ced9bac6683abd0e69ead111de2d49b20a Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:31:45 -0400 Subject: [PATCH 09/10] refactor --- .../youtube/patches/ReturnYouTubeDislikePatch.java | 13 ++++++------- .../requests/ReturnYouTubeDislikeApi.java | 2 +- 2 files changed, 7 insertions(+), 8 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 a43c263d7b..8aa2c5ca5a 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 @@ -242,16 +242,15 @@ private static CharSequence onLithoTextLoaded(@NonNull Object conversionContext, return original; // No need to check for Shorts in the context. } - // Check the original string first, since it's almost always shorter than the context. - if (!Utils.containsNumber(original) - && conversionContextString.contains("|shorts_like_button.eml")) { - Logger.printDebug(() -> "Replacing hidden likes count"); - return getShortsSpan(original, false); - } - if (conversionContextString.contains("|shorts_dislike_button.eml")) { return getShortsSpan(original, true); } + + if (conversionContextString.contains("|shorts_like_button.eml") + && !Utils.containsNumber(original)) { + Logger.printDebug(() -> "Replacing hidden likes count"); + return getShortsSpan(original, false); + } } catch (Exception ex) { Logger.printException(() -> "onLithoTextLoaded failure", ex); } 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 bc729e4794..07fded79d2 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 @@ -197,7 +197,7 @@ private static boolean checkIfRateLimitWasHit(int httpResponseCode) { return httpResponseCode == HTTP_STATUS_CODE_RATE_LIMIT; } - @SuppressWarnings("NonAtomicOperationOnVolatileField") // Don't care, fields are estimates. + @SuppressWarnings("NonAtomicOperationOnVolatileField") // Don't care, fields are only estimates. private static void updateRateLimitAndStats(long timeNetworkCallStarted, boolean connectionError, boolean rateLimitHit) { if (connectionError && rateLimitHit) { throw new IllegalArgumentException(); From abbe68f2a2af56a008669b87765350efa949fa1b Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sun, 1 Sep 2024 01:21:26 -0400 Subject: [PATCH 10/10] perf: Specify no body data is sent --- .../youtube/requests/Requester.java | 3 +++ .../requests/ReturnYouTubeDislikeApi.java | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/requests/Requester.java b/app/src/main/java/app/revanced/integrations/youtube/requests/Requester.java index ef409b52d3..c62e34f591 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/requests/Requester.java +++ b/app/src/main/java/app/revanced/integrations/youtube/requests/Requester.java @@ -23,6 +23,9 @@ public static HttpURLConnection getConnectionFromRoute(String apiUrl, Route rout public static HttpURLConnection getConnectionFromCompiledRoute(String apiUrl, Route.CompiledRoute route) throws IOException { String url = apiUrl + route.getCompiledRoute(); HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + // Request data is in the URL parameters and no body is sent. + // The calling code must set a length if using a request body. + connection.setFixedLengthStreamingMode(0); connection.setRequestMethod(route.getMethod().name()); String agentString = System.getProperty("http.agent") + "; ReVanced/" + Utils.getAppVersionName() 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 07fded79d2..cb211ea501 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 @@ -368,10 +368,12 @@ private static String confirmRegistration(String userId, String solution) { applyCommonPostRequestSettings(connection); String jsonInputString = "{\"solution\": \"" + solution + "\"}"; + byte[] body = jsonInputString.getBytes(StandardCharsets.UTF_8); + connection.setFixedLengthStreamingMode(body.length); try (OutputStream os = connection.getOutputStream()) { - byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); + os.write(body); } + final int responseCode = connection.getResponseCode(); if (checkIfRateLimitWasHit(responseCode)) { connection.disconnect(); // disconnect, as no more connections will be made for a little while @@ -440,9 +442,10 @@ public static boolean sendVote(String videoId, ReturnYouTubeDislike.Vote vote) { applyCommonPostRequestSettings(connection); String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote.value + "\"}"; + byte[] body = voteJsonString.getBytes(StandardCharsets.UTF_8); + connection.setFixedLengthStreamingMode(body.length); try (OutputStream os = connection.getOutputStream()) { - byte[] input = voteJsonString.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); + os.write(body); } final int responseCode = connection.getResponseCode(); @@ -490,10 +493,12 @@ private static boolean confirmVote(String videoId, String userId, String solutio applyCommonPostRequestSettings(connection); String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}"; + byte[] body = jsonInputString.getBytes(StandardCharsets.UTF_8); + connection.setFixedLengthStreamingMode(body.length); try (OutputStream os = connection.getOutputStream()) { - byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); + os.write(body); } + final int responseCode = connection.getResponseCode(); if (checkIfRateLimitWasHit(responseCode)) { connection.disconnect(); // disconnect, as no more connections will be made for a little while