Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

Commit

Permalink
fix(YouTube - Return YouTube Dislike): Disabling `Show dislikes in Sh…
Browse files Browse the repository at this point in the history
…orts` disabled dislikes everywhere (#11)
  • Loading branch information
rufusin authored May 7, 2024
1 parent 4093014 commit 1b317db
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
@GuardedBy("itself")
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
/**
* Number of video id's to keep track of for searching thru the buffer.
* Number of video id's to keep track of for searching through the buffer.
* A minimum value of 3 should be sufficient, but check a few more just in case.
*/
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
Expand Down Expand Up @@ -64,6 +64,7 @@ public ReturnYouTubeDislikeFilterPatch() {
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) {
try {
if (!isShortAndOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) {
Expand Down Expand Up @@ -110,7 +111,7 @@ boolean isFiltered(String path, @Nullable String identifier, String allValue, by
String matchedVideoId = findVideoId(protobufBufferArray);
// Matched video will be null if in incognito mode.
// Must pass a null id to correctly clear out the current video data.
// Otherwise if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,
// Otherwise, if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,
// the new incognito Short will show the old prior data.
ReturnYouTubeDislikePatch.setLastLithoShortsVideoId(matchedVideoId);
}
Expand All @@ -129,4 +130,4 @@ private String findVideoId(byte[] protobufBufferArray) {
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
Expand All @@ -25,6 +26,7 @@
import app.revanced.integrations.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.integrations.youtube.patches.video.VideoInformation;
import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.integrations.youtube.settings.SettingsEnum;
import app.revanced.integrations.youtube.shared.PlayerType;
import app.revanced.integrations.youtube.utils.LogHelper;
Expand All @@ -41,7 +43,7 @@
* <p>
* A (yet to be implemented) solution that fixes this problem. Any one of:
* - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes text asynchronously.
* - Find a way to force Litho to rebuild it's component tree,
* - Find a way to force Litho to rebuild its component tree,
* and use that hook to force the shorts dislikes to update after the fetch is completed.
* - Hook into the dislikes button image view, and replace the dislikes thumb down image with a
* generated image of the number of dislikes, then update the image asynchronously. This Could
Expand Down Expand Up @@ -81,17 +83,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() {
Expand Down Expand Up @@ -220,7 +221,7 @@ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
* This method can be called multiple times for the same UI element (including after dislikes was added).
*
* @param original Original char sequence was created or reused by Litho.
* @param original Original char sequence was created or reused by Litho.
* @param isRollingNumber If the span is for a Rolling Number.
* @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes.
*/
Expand Down Expand Up @@ -312,18 +313,14 @@ public static CharSequence onCharSequenceLoaded(@NonNull Object conversionContex
return original;
}
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
// 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;
}

final boolean fetchDislikeIncognito =
conversionContextString.contains("|shorts_dislike_button.eml|")
&& isIncognito;
final boolean fetchDislikeLiveStream =
conversionContextString.contains("immersive_live_video_action_bar.eml")
conversionContextString.contains("|immersive_live_video_action_bar.eml|")
&& conversionContextString.contains("|dislike_button.eml|");

if (fetchDislikeIncognito) {
Expand Down Expand Up @@ -364,9 +361,10 @@ public static String onRollingNumberLoaded(@NonNull Object conversionContext,
@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) {
LogHelper.printException(() -> "onRollingNumberLoaded failure", ex);
Expand Down Expand Up @@ -400,6 +398,7 @@ public static float onRollingNumberMeasured(String text, float measuredTextWidth
} catch (Exception ex) {
LogHelper.printException(() -> "onRollingNumberMeasured failure", ex);
}

return measuredTextWidth;
}

Expand All @@ -417,11 +416,12 @@ private static void addRollingNumberPatchChanges(TextView view) {
} 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.
// The styled span applied to the view should always have the same bounds,
// but use this feature just in case the measurements are somehow off by a few pixels.
Expand Down Expand Up @@ -490,7 +490,7 @@ public static CharSequence updateRollingNumber(TextView view, CharSequence origi
}

//
// Non litho Shorts player.
// Non-litho Shorts player.
//

/**
Expand All @@ -507,7 +507,9 @@ public static CharSequence updateRollingNumber(TextView view, CharSequence origi
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();

private static void clearRemovedShortsTextViews() {
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // YouTube requires Android N or greater
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
}
}

/**
Expand Down Expand Up @@ -621,7 +623,7 @@ private static boolean isShortTextViewOnScreen(@NonNull View view) {


//
// Video Id and voting hooks (all players).
// Video ID and voting hooks (all players).
//

private static volatile boolean lastPlayerResponseWasShort;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.graphics.drawable.shapes.OvalShape;
import android.graphics.drawable.shapes.RectShape;
import android.icu.text.CompactDecimalFormat;
import android.os.Build;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
Expand Down Expand Up @@ -150,7 +151,7 @@ public enum Vote {
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;

Expand Down Expand Up @@ -184,12 +185,12 @@ public enum Vote {

/**
* 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) {
Expand Down Expand Up @@ -250,7 +251,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
: "\u200E"; // u200E = left to right character
final Spannable leftSeparatorSpan;
if (isRollingNumber) {
leftSeparatorSpan = new SpannableString(leftSeparatorString);
leftSeparatorSpan = new SpannableString(leftSeparatorString);
} else {
leftSeparatorString += " ";
leftSeparatorSpan = new SpannableString(leftSeparatorString);
Expand Down Expand Up @@ -350,18 +351,23 @@ private static SpannableString newSpanUsingStylingOfAnotherSpan(@NonNull Spanned
}

private static String formatDislikeCount(long dislikeCount) {
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(ReVancedUtils.getContext()).getResources().getConfiguration().locale;
LogHelper.printDebug(() -> "Locale: " + locale);
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
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(ReVancedUtils.getContext()).getResources().getConfiguration().locale;
LogHelper.printDebug(() -> "Locale: " + locale);
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
}
return dislikeCountFormatter.format(dislikeCount);
}
return dislikeCountFormatter.format(dislikeCount);
}

// Will never be reached, as the oldest supported YouTube app requires Android N or greater.
return String.valueOf(dislikeCount);
}

private static String formatDislikePercentage(float dislikePercentage) {
Expand All @@ -385,13 +391,15 @@ public static ReturnYouTubeDislike getFetchForVideoId(@Nullable String videoId)
Objects.requireNonNull(videoId);
synchronized (fetchCache) {
// Remove any expired entries.
final long now = System.currentTimeMillis();
fetchCache.values().removeIf(value -> {
final boolean expired = value.isExpired(now);
if (expired)
LogHelper.printDebug(() -> "Removing expired fetch: " + value.videoId);
return expired;
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final long now = System.currentTimeMillis();
fetchCache.values().removeIf(value -> {
final boolean expired = value.isExpired(now);
if (expired)
LogHelper.printDebug(() -> "Removing expired fetch: " + value.videoId);
return expired;
});
}

ReturnYouTubeDislike fetch = fetchCache.get(videoId);
if (fetch == null) {
Expand All @@ -403,7 +411,7 @@ public static ReturnYouTubeDislike getFetchForVideoId(@Nullable String videoId)
}

/**
* 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) {
Expand Down Expand Up @@ -645,8 +653,8 @@ class VerticallyCenteredImageSpan extends ImageSpan {

/**
* @param useOriginalWidth Use the original layout width of the text this span is applied to,
* and not the bounds of the Drawable. Drawable is always displayed using it's own bounds,
* and this setting only affects the layout width of the entire span.
* and not the bounds of the Drawable. Drawable is always displayed using it's own bounds,
* and this setting only affects the layout width of the entire span.
*/
public VerticallyCenteredImageSpan(Drawable drawable, boolean useOriginalWidth) {
super(drawable);
Expand Down
Loading

0 comments on commit 1b317db

Please sign in to comment.