From 178b90b49089763aa3d217c8d88cb70868fdf7a5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 22 Feb 2023 16:39:49 +0100 Subject: [PATCH 01/10] ci: fix backmerge direction Signed-off-by: oSumAtrIX --- .releaserc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.releaserc b/.releaserc index f8081831..02d9c7fa 100644 --- a/.releaserc +++ b/.releaserc @@ -34,7 +34,7 @@ [ "@saithodev/semantic-release-backmerge", { - backmergeBranches: [{"from": "dev", "to": "main"}], + backmergeBranches: [{"from": "main", "to": "dev"}], clearWorkspace: true } ] From ae862cbac6f9a9b717617469d202b48923a1d3b4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 23 Feb 2023 13:30:49 +0100 Subject: [PATCH 02/10] feat(youtube/general-ads): hide quick actions in fullscreen Signed-off-by: oSumAtrIX --- .../java/app/revanced/integrations/patches/GeneralAdsPatch.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java index 489569ca..1be083de 100644 --- a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java @@ -93,6 +93,7 @@ public final class GeneralAdsPatch extends Filter { channelMemberShelf ); + var quickActions = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "quick_actions"); var carouselAd = new BlockRule(SettingsEnum.ADREMOVER_GENERAL_ADS_REMOVAL, "carousel_ad" ); @@ -106,6 +107,7 @@ public final class GeneralAdsPatch extends Filter { this.identifierRegister.registerAll( shorts, graySeparator, + quickActions, carouselAd ); } From cfc571c12cb012b86c8bfa8bf7df1c77b9711a21 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 23 Feb 2023 13:31:15 +0100 Subject: [PATCH 03/10] feat(youtube/general-ads): hide related videos in quick action Signed-off-by: oSumAtrIX --- .../java/app/revanced/integrations/patches/GeneralAdsPatch.java | 2 ++ .../java/app/revanced/integrations/settings/SettingsEnum.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java index 1be083de..44bf0713 100644 --- a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java @@ -40,6 +40,7 @@ public final class GeneralAdsPatch extends Filter { var webLinkPanel = new BlockRule(SettingsEnum.ADREMOVER_WEB_SEARCH_RESULTS, "web_link_panel"); var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf"); var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar"); + var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "fullscreen_related_videos"); var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR, "cell_divider" // layout residue (gray line above the buttoned ad), ); @@ -78,6 +79,7 @@ public final class GeneralAdsPatch extends Filter { movieAds, chapterTeaser, communityGuidelines, + relatedVideos, compactBanner, inFeedSurvey, viewProducts, diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 480e19b8..73b99d20 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -56,6 +56,8 @@ public enum SettingsEnum { ADREMOVER_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", true, ReturnType.BOOLEAN), ADREMOVER_HORIZONTAL_VIDEO_SHELF("revanced_horizontal_video_shelf", true, ReturnType.BOOLEAN), ADREMOVER_CHANNEL_BAR("revanced_hide_channel_bar", false, ReturnType.BOOLEAN), + ADREMOVER_QUICK_ACTIONS("revanced_hide_quick_actions", false, ReturnType.BOOLEAN), + ADREMOVER_RELATED_VIDEOS("revanced_hide_related_videos", false, ReturnType.BOOLEAN), // Action buttons HIDE_LIKE_BUTTON("revanced_hide_like_button", false, ReturnType.BOOLEAN, false), From fb1a69a7ba6d925b1f5b1b4fa960d94fe33169a6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 23 Feb 2023 12:33:42 +0000 Subject: [PATCH 04/10] chore(release): 0.99.0-dev.2 [skip ci] # [0.99.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.1...v0.99.0-dev.2) (2023-02-23) ### Features * **youtube/general-ads:** hide quick actions in fullscreen ([ae862cb](https://github.com/revanced/revanced-integrations/commit/ae862cbac6f9a9b717617469d202b48923a1d3b4)) * **youtube/general-ads:** hide related videos in quick action ([cfc571c](https://github.com/revanced/revanced-integrations/commit/cfc571c12cb012b86c8bfa8bf7df1c77b9711a21)) --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb9eac4..eb112fcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# [0.99.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.1...v0.99.0-dev.2) (2023-02-23) + + +### Features + +* **youtube/general-ads:** hide quick actions in fullscreen ([ae862cb](https://github.com/revanced/revanced-integrations/commit/ae862cbac6f9a9b717617469d202b48923a1d3b4)) +* **youtube/general-ads:** hide related videos in quick action ([cfc571c](https://github.com/revanced/revanced-integrations/commit/cfc571c12cb012b86c8bfa8bf7df1c77b9711a21)) + # [0.99.0-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.98.0...v0.99.0-dev.1) (2023-02-22) diff --git a/gradle.properties b/gradle.properties index 9c582b11..18d18e1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs = -Xmx2048m android.useAndroidX = true -version = 0.99.0-dev.1 +version = 0.99.0-dev.2 From 0aef5e60e280b63490dac8d1b706e896fdf913a2 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Fri, 24 Feb 2023 05:59:32 +0400 Subject: [PATCH 05/10] feat(youtube/return-youtube-dislike): support for shorts (#312) Signed-off-by: oSumAtrIX Co-authored-by: oSumAtrIX --- .../patches/ReturnYouTubeDislikePatch.java | 27 +- .../ReturnYouTubeDislike.java | 512 +++++++++++------- .../requests/RYDVoteData.java | 88 ++- .../requests/ReturnYouTubeDislikeApi.java | 68 ++- .../integrations/shared/PlayerType.kt | 9 +- 5 files changed, 433 insertions(+), 271 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index d30798e3..69a5def1 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -1,22 +1,25 @@ package app.revanced.integrations.patches; -import java.util.concurrent.atomic.AtomicReference; - +import android.text.Spanned; import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; +import java.util.concurrent.atomic.AtomicReference; + /** * Used by app.revanced.patches.youtube.layout.returnyoutubedislike.patch.ReturnYouTubeDislikePatch */ public class ReturnYouTubeDislikePatch { /** - * Called when the video id changes + * Injection point */ public static void newVideoLoaded(String videoId) { ReturnYouTubeDislike.newVideoLoaded(videoId); } /** + * Injection point + * * Called when a litho text component is created */ public static void onComponentCreated(Object conversionContext, AtomicReference textRef) { @@ -24,16 +27,22 @@ public class ReturnYouTubeDislikePatch { } /** + * Injection point + * + * Called when a Shorts dislike Spannable is created + */ + public static Spanned onShortsComponentCreated(Spanned dislike) { + return ReturnYouTubeDislike.onShortsComponentCreated(dislike); + } + + /** + * Injection point + * * Called when the like/dislike button is clicked * * @param vote -1 (dislike), 0 (none) or 1 (like) */ public static void sendVote(int vote) { - for (ReturnYouTubeDislike.Vote v : ReturnYouTubeDislike.Vote.values()) { - if (v.value == vote) { - ReturnYouTubeDislike.sendVote(v); - return; - } - } + ReturnYouTubeDislike.sendVote(vote); } } diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java index ab324401..3903ae33 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -1,40 +1,31 @@ package app.revanced.integrations.returnyoutubedislike; -import static app.revanced.integrations.sponsorblock.StringRef.str; - import android.icu.text.CompactDecimalFormat; import android.os.Build; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.TextPaint; +import android.text.*; import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.ScaleXSpan; - import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import java.text.NumberFormat; -import java.util.Locale; -import java.util.Objects; -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.atomic.AtomicReference; - import app.revanced.integrations.returnyoutubedislike.requests.RYDVoteData; import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; -import app.revanced.integrations.utils.SharedPrefHelper; import app.revanced.integrations.utils.ThemeHelper; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import static app.revanced.integrations.sponsorblock.StringRef.str; + public class ReturnYouTubeDislike { /** * Maximum amount of time to block the UI from updates while waiting for network call to complete. @@ -42,31 +33,56 @@ public class ReturnYouTubeDislike { * Must be less than 5 seconds, as per: * https://developer.android.com/topic/performance/vitals/anr */ - private static final long MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE = 4000; + private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE = 4000; + + /** + * Separator character to use for segmented like/dislike + */ + private static final char MIDDLE_SEPARATOR_CHARACTER = '•'; /** * Used to send votes, one by one, in the same order the user created them */ private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor(); - // Must be volatile, since this is read/write from different threads - private static volatile boolean isEnabled = SettingsEnum.RYD_ENABLED.getBoolean(); - /** * Used to guard {@link #currentVideoId} and {@link #voteFetchFuture}, * as multiple threads access this class. */ private static final Object videoIdLockObject = new Object(); + @Nullable @GuardedBy("videoIdLockObject") private static String currentVideoId; + + /** + * If {@link #currentVideoId} and the RYD data is for the last shorts loaded + */ + private static volatile boolean lastVideoLoadedWasShort; + /** * Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes */ + @Nullable @GuardedBy("videoIdLockObject") private static Future voteFetchFuture; + /** + * Original dislike span, before modifications. + * Required for segmented layout + */ + @Nullable + @GuardedBy("videoIdLockObject") + private static Spanned originalDislikeSpan; + + /** + * Replacement like/dislike span that includes formatted dislikes and is ready to display + */ + @Nullable + @GuardedBy("videoIdLockObject") + private static Spanned replacementLikeDislikeSpan; + public enum Vote { LIKE(1), DISLIKE(-1), @@ -95,44 +111,58 @@ public class ReturnYouTubeDislike { private static NumberFormat dislikePercentageFormatter; public static void onEnabledChange(boolean enabled) { - synchronized (videoIdLockObject) { - isEnabled = enabled; - if (!enabled) { - // must clear old values, to protect against using stale data - // if the user re-enables RYD while watching a video - LogHelper.printDebug(() -> "Clearing previously fetched RYD vote data"); - currentVideoId = null; - voteFetchFuture = null; - } + if (!enabled) { + // Must clear old values, to protect against using stale data + // if the user re-enables RYD while watching a video. + setCurrentVideoId(null); } } + private static void setCurrentVideoId(@Nullable String videoId) { + synchronized (videoIdLockObject) { + if (videoId == null && currentVideoId != null) { + LogHelper.printDebug(() -> "Clearing data"); + } + currentVideoId = videoId; + lastVideoLoadedWasShort = false; + voteFetchFuture = null; + originalDislikeSpan = null; + replacementLikeDislikeSpan = null; + } + } + + @Nullable private static String getCurrentVideoId() { synchronized (videoIdLockObject) { return currentVideoId; } } + @Nullable private static Future getVoteFetchFuture() { synchronized (videoIdLockObject) { return voteFetchFuture; } } - // It is unclear if this method is always called on the main thread (since the YouTube app is the one making the call) - // treat this as if any thread could call this method - public static void newVideoLoaded(String videoId) { - if (!isEnabled) return; + public static void newVideoLoaded(@NonNull String videoId) { + if (!SettingsEnum.RYD_ENABLED.getBoolean()) return; + try { Objects.requireNonNull(videoId); + PlayerType currentPlayerType = PlayerType.getCurrent(); if (currentPlayerType == PlayerType.INLINE_MINIMAL) { - LogHelper.printDebug(() -> "Ignoring inline playback of video: "+ videoId); + LogHelper.printDebug(() -> "Ignoring inline playback of video: " + videoId); + setCurrentVideoId(null); return; } - LogHelper.printDebug(() -> " new video loaded: " + videoId + " playerType: " + currentPlayerType); synchronized (videoIdLockObject) { - currentVideoId = videoId; + if (videoId.equals(currentVideoId)) { + return; // already loaded + } + LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType); + setCurrentVideoId(videoId); // no need to wrap the call in a try/catch, // as any exceptions are propagated out in the later Future#Get call voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId)); @@ -144,86 +174,149 @@ public class ReturnYouTubeDislike { /** * 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) - * This code should avoid needlessly replacing the same UI element with identical versions. */ - public static void onComponentCreated(Object conversionContext, AtomicReference textRef) { - if (!isEnabled) return; + public static void onComponentCreated(@NonNull Object conversionContext, @NonNull AtomicReference textRef) { + if (!SettingsEnum.RYD_ENABLED.getBoolean()) return; + + // do not set lastVideoLoadedWasShort to false. It will be cleared when the next regular video is loaded. + if (lastVideoLoadedWasShort) { + return; + } + if (PlayerType.getCurrent().isNoneOrHidden()) { + return; + } + + String conversionContextString = conversionContext.toString(); + final boolean isSegmentedButton; + if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) { + isSegmentedButton = true; + } else if (conversionContextString.contains("|dislike_button.eml|")) { + isSegmentedButton = false; + } else { + return; + } try { - String conversionContextString = conversionContext.toString(); - - final boolean isSegmentedButton; - if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) { - isSegmentedButton = true; - } else if (conversionContextString.contains("|dislike_button.eml|")) { - isSegmentedButton = false; - } else { - return; - } - - // Have to block the current thread until fetching is done - // There's no known way to edit the text after creation yet - RYDVoteData votingData; - long fetchStartTime = 0; - try { - Future fetchFuture = getVoteFetchFuture(); - if (fetchFuture == null) { - LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)"); - return; - } - if (SettingsEnum.DEBUG.getBoolean() && !fetchFuture.isDone()) { - fetchStartTime = System.currentTimeMillis(); - } - votingData = fetchFuture.get(MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - LogHelper.printDebug(() -> "UI timed out waiting for fetch votes to complete"); - return; - } finally { - recordTimeUISpentWaitingForNetworkCall(fetchStartTime); - } - if (votingData == null) { - LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)"); - return; - } - - if (updateDislike(textRef, isSegmentedButton, votingData)) { - LogHelper.printDebug(() -> "Updated dislike span to: " + textRef.get()); - } else { - LogHelper.printDebug(() -> "Ignoring already updated dislike span: " + textRef.get()); + Spanned replacement = waitForFetchAndUpdateReplacementSpan((Spanned) textRef.get(), isSegmentedButton); + if (replacement != null) { + textRef.set(replacement); } } catch (Exception ex) { LogHelper.printException(() -> "Error while trying to update dislikes", ex); } } - public static void sendVote(Vote vote) { - if (!isEnabled) return; + public static void sendVote(int vote) { + if (!SettingsEnum.RYD_ENABLED.getBoolean()) return; + try { - Objects.requireNonNull(vote); - - if (PlayerType.getCurrent() == PlayerType.NONE) { // should occur if shorts is playing - LogHelper.printDebug(() -> "Ignoring vote during Shorts playback"); - return; + for (ReturnYouTubeDislike.Vote v : ReturnYouTubeDislike.Vote.values()) { + if (v.value == vote) { + ReturnYouTubeDislike.sendVote(v); + return; + } } - if (SharedPrefHelper.getBoolean(SharedPrefHelper.SharedPrefNames.YOUTUBE, "user_signed_out", true)) { - LogHelper.printDebug(() -> "User is logged out, ignoring voting"); - return; + LogHelper.printException(() -> "Unknown vote type: " + vote); + } catch (Exception ex) { + LogHelper.printException(() -> "sendVote failure", ex); + } + } + + public static Spanned onShortsComponentCreated(Spanned span) { + try { + if (SettingsEnum.RYD_ENABLED.getBoolean()) { + lastVideoLoadedWasShort = true; + Spanned replacement = waitForFetchAndUpdateReplacementSpan(span, false); + if (replacement != null) { + return replacement; + } + } + } catch (Exception ex) { + LogHelper.printException(() -> "onShortsComponentCreated failure", ex); + } + return span; + } + + private static boolean isPreviouslyCreatedSegmentedSpan(Spanned span) { + return span.toString().indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1; + } + + /** + * @return NULL if the span does not need changing or if RYD is not available + */ + @Nullable + private static Spanned waitForFetchAndUpdateReplacementSpan(Spanned oldSpannable, boolean isSegmentedButton) { + if (oldSpannable == null) { + LogHelper.printDebug(() -> "Cannot add dislikes (injection code was called with null Span)"); + return null; + } + // Must block the current thread until fetching is done + // There's no known way to edit the text after creation yet + long fetchStartTime = 0; + try { + synchronized (videoIdLockObject) { + if (oldSpannable == replacementLikeDislikeSpan) { + LogHelper.printDebug(() -> "Ignoring previously created dislike span"); + return null; + } + if (isSegmentedButton) { + if (isPreviouslyCreatedSegmentedSpan(oldSpannable)) { + // need to recreate using original, as oldSpannable has prior outdated dislike values + oldSpannable = originalDislikeSpan; + } else { + originalDislikeSpan = oldSpannable; // most up to date original + } + } } + Future fetchFuture = getVoteFetchFuture(); + if (fetchFuture == null) { + LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)"); + return null; + } + if (SettingsEnum.DEBUG.getBoolean() && !fetchFuture.isDone()) { + fetchStartTime = System.currentTimeMillis(); + } + RYDVoteData votingData = fetchFuture.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS); + if (votingData == null) { + LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)"); + return null; + } + + Spanned replacement = createDislikeSpan(oldSpannable, isSegmentedButton, votingData); + synchronized (videoIdLockObject) { + replacementLikeDislikeSpan = replacement; + } + final Spanned oldSpannableLogging = oldSpannable; + LogHelper.printDebug(() -> "Replaced: '" + oldSpannableLogging + "' with: '" + replacement + "'"); + return replacement; + } catch (TimeoutException e) { + LogHelper.printDebug(() -> "UI timed out while waiting for fetch votes to complete"); // show no toast + } catch (Exception e) { + LogHelper.printException(() -> "createReplacementSpan failure", e); // should never happen + } finally { + recordTimeUISpentWaitingForNetworkCall(fetchStartTime); + } + return null; + } + + public static void sendVote(@NonNull Vote vote) { + ReVancedUtils.verifyOnMainThread(); + Objects.requireNonNull(vote); + try { // Must make a local copy of videoId, since it may change between now and when the vote thread runs String videoIdToVoteFor = getCurrentVideoId(); - if (videoIdToVoteFor == null) { - // user enabled RYD after starting playback of a video - LogHelper.printException(() -> "Cannot vote, current video is is null (user enabled RYD while video was playing?)", + if (videoIdToVoteFor == null || (lastVideoLoadedWasShort && !PlayerType.getCurrent().isNoneOrHidden())) { + // User enabled RYD after starting playback of a video. + // Or shorts was loaded with regular video present, then shorts was closed, and then user voted on the now visible original video + LogHelper.printException(() -> "Cannot send vote", null, str("revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted")); return; } voteSerialExecutor.execute(() -> { - // must wrap in try/catch to properly log exceptions - try { + try { // must wrap in try/catch to properly log exceptions String userId = getUserId(); if (userId != null) { ReturnYouTubeDislikeApi.sendVote(videoIdToVoteFor, userId, vote); @@ -232,8 +325,27 @@ public class ReturnYouTubeDislike { LogHelper.printException(() -> "Failed to send vote", ex); } }); + + // update the downloaded vote data + synchronized (videoIdLockObject) { + replacementLikeDislikeSpan = null; // ui values need updating + } + + Future future = getVoteFetchFuture(); + if (future == null) { + LogHelper.printException(() -> "Cannot update UI dislike count - vote fetch is null"); + return; + } + // the future should always be completed before user can like/dislike, but use a timeout just in case + RYDVoteData voteData = future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS); + if (voteData == null) { + // RYD fetch failed + LogHelper.printDebug(() -> "Cannot update UI (vote data not available)"); + return; + } + voteData.updateUsingVote(vote); } catch (Exception ex) { - LogHelper.printException(() -> "Error while trying to send vote", ex); + LogHelper.printException(() -> "Error trying to send vote", ex); } } @@ -261,122 +373,104 @@ public class ReturnYouTubeDislike { /** * @param isSegmentedButton if UI is using the segmented single UI component for both like and dislike - * @return false, if the text reference already has dislike information and no changes were made. */ - private static boolean updateDislike(AtomicReference textRef, boolean isSegmentedButton, RYDVoteData voteData) { - Spannable oldSpannable = (Spannable) textRef.get(); - String oldLikesString = oldSpannable.toString(); - Spannable replacementSpannable; + private static Spanned createDislikeSpan(Spanned oldSpannable, boolean isSegmentedButton, RYDVoteData voteData) { + if (!isSegmentedButton) { + // simple replacement of 'dislike' with a number/percentage + return newSpannableWithDislikes(oldSpannable, voteData); + } // note: some locales use right to left layout (arabic, hebrew, etc), // and care must be taken to retain the existing RTL encoding character on the likes string // otherwise text will incorrectly show as left to right // if making changes to this code, change device settings to a RTL language and verify layout is correct + String oldLikesString = oldSpannable.toString(); - if (!isSegmentedButton) { - // simple replacement of 'dislike' with a number/percentage - if (stringContainsNumber(oldLikesString)) { - // already is a number, and was modified in a previous call to this method - return false; - } - replacementSpannable = newSpannableWithDislikes(oldSpannable, voteData); - } else { - final boolean useCompactLayout = SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean(); - // if compact layout, use a "half space" character - String middleSegmentedSeparatorString = useCompactLayout ? "\u2009 • \u2009" : " • "; + // 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)) { + // 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) + // + // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw + // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw + // + // discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530 - if (oldLikesString.contains(middleSegmentedSeparatorString)) { - return false; // dislikes was previously added - } - - // 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)) { - // 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) - // - // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw - // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw - // - // discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530 - - // - // Change the "Likes" string to show that likes and dislikes are hidden - // - String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner"); - if (hiddenMessageString.equals(oldLikesString)) { - return false; - } - replacementSpannable = newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString); - } else { - Spannable likesSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikesString); - - // middle separator - Spannable middleSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, middleSegmentedSeparatorString); - final int separatorColor = ThemeHelper.isDarkTheme() - ? 0x29AAAAAA // transparent dark gray - : 0xFFD9D9D9; // light gray - addSpanStyling(middleSeparatorSpan, new ForegroundColorSpan(separatorColor)); - CharacterStyle noAntiAliasingStyle = new CharacterStyle() { - @Override - public void updateDrawState(TextPaint tp) { - tp.setAntiAlias(false); // draw without anti-aliasing, to give a sharper edge - } - }; - addSpanStyling(middleSeparatorSpan, noAntiAliasingStyle); - - Spannable dislikeSpan = newSpannableWithDislikes(oldSpannable, voteData); - - SpannableStringBuilder builder = new SpannableStringBuilder(); - - if (!useCompactLayout) { - String leftSegmentedSeparatorString = ReVancedUtils.isRightToLeftTextLayout() ? "\u200F| " : "| "; - Spannable leftSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, leftSegmentedSeparatorString); - addSpanStyling(leftSeparatorSpan, new ForegroundColorSpan(separatorColor)); - addSpanStyling(leftSeparatorSpan, noAntiAliasingStyle); - - // Use a left separator with a larger font and visually match the stock right separator. - // But with a larger font, the entire span (including the like/dislike text) becomes shifted downward. - // To correct this, use additional spans to move the alignment back upward by a relative amount. - setSegmentedAdjustmentValues(); - class RelativeVerticalOffsetSpan extends CharacterStyle { - final float relativeVerticalShiftRatio; - - RelativeVerticalOffsetSpan(float relativeVerticalShiftRatio) { - this.relativeVerticalShiftRatio = relativeVerticalShiftRatio; - } - - @Override - public void updateDrawState(TextPaint tp) { - tp.baselineShift -= (int) (relativeVerticalShiftRatio * tp.getFontMetrics().top); - } - } - // each section needs it's own Relative span, otherwise alignment is wrong - addSpanStyling(leftSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedLeftSeparatorVerticalShiftRatio)); - - addSpanStyling(likesSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); - addSpanStyling(middleSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); - addSpanStyling(dislikeSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); - - // important: must add size scaling after vertical offset (otherwise alignment gets off) - addSpanStyling(leftSeparatorSpan, new RelativeSizeSpan(segmentedLeftSeparatorFontRatio)); - addSpanStyling(leftSeparatorSpan, new ScaleXSpan(segmentedLeftSeparatorHorizontalScaleRatio)); - // middle separator does not need resizing - - builder.append(leftSeparatorSpan); - } - - builder.append(likesSpan); - builder.append(middleSeparatorSpan); - builder.append(dislikeSpan); - replacementSpannable = new SpannableString(builder); - } + // + // 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); } - textRef.set(replacementSpannable); - return true; + Spannable likesSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikesString); + + // middle separator + final boolean useCompactLayout = SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean(); + final int separatorColor = ThemeHelper.isDarkTheme() + ? 0x29AAAAAA // transparent dark gray + : 0xFFD9D9D9; // light gray + + String middleSegmentedSeparatorString = useCompactLayout + ? "\u2009 " + MIDDLE_SEPARATOR_CHARACTER + " \u2009" // u2009 = "half space" character + : " " + MIDDLE_SEPARATOR_CHARACTER + " "; + Spannable middleSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, middleSegmentedSeparatorString); + addSpanStyling(middleSeparatorSpan, new ForegroundColorSpan(separatorColor)); + CharacterStyle noAntiAliasingStyle = new CharacterStyle() { + @Override + public void updateDrawState(TextPaint tp) { + tp.setAntiAlias(false); // draw without anti-aliasing, to give a sharper edge + } + }; + addSpanStyling(middleSeparatorSpan, noAntiAliasingStyle); + + Spannable dislikeSpan = newSpannableWithDislikes(oldSpannable, voteData); + + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (!useCompactLayout) { + String leftSegmentedSeparatorString = ReVancedUtils.isRightToLeftTextLayout() ? "\u200F| " : "| "; // u200f = right to left character + Spannable leftSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, leftSegmentedSeparatorString); + addSpanStyling(leftSeparatorSpan, new ForegroundColorSpan(separatorColor)); + addSpanStyling(leftSeparatorSpan, noAntiAliasingStyle); + + // Use a left separator with a larger font and visually match the stock right separator. + // But with a larger font, the entire span (including the like/dislike text) becomes shifted downward. + // To correct this, use additional spans to move the alignment back upward by a relative amount. + setSegmentedAdjustmentValues(); + class RelativeVerticalOffsetSpan extends CharacterStyle { + final float relativeVerticalShiftRatio; + + RelativeVerticalOffsetSpan(float relativeVerticalShiftRatio) { + this.relativeVerticalShiftRatio = relativeVerticalShiftRatio; + } + + @Override + public void updateDrawState(TextPaint tp) { + tp.baselineShift -= (int) (relativeVerticalShiftRatio * tp.getFontMetrics().top); + } + } + // each section needs it's own Relative span, otherwise alignment is wrong + addSpanStyling(leftSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedLeftSeparatorVerticalShiftRatio)); + + addSpanStyling(likesSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); + addSpanStyling(middleSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); + addSpanStyling(dislikeSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio)); + + // important: must add size scaling after vertical offset (otherwise alignment gets off) + addSpanStyling(leftSeparatorSpan, new RelativeSizeSpan(segmentedLeftSeparatorFontRatio)); + addSpanStyling(leftSeparatorSpan, new ScaleXSpan(segmentedLeftSeparatorHorizontalScaleRatio)); + // middle separator does not need resizing + + builder.append(leftSeparatorSpan); + } + + builder.append(likesSpan); + builder.append(middleSeparatorSpan); + builder.append(dislikeSpan); + return new SpannableString(builder); } private static boolean segmentedValuesSet = false; @@ -455,14 +549,14 @@ public class ReturnYouTubeDislike { destination.setSpan(styling, 0, destination.length(), 0); } - private static Spannable newSpannableWithDislikes(Spannable sourceStyling, RYDVoteData voteData) { + private static Spannable newSpannableWithDislikes(Spanned sourceStyling, RYDVoteData voteData) { return newSpanUsingStylingOfAnotherSpan(sourceStyling, SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean() - ? formatDislikePercentage(voteData.dislikePercentage) - : formatDislikeCount(voteData.dislikeCount)); + ? formatDislikePercentage(voteData.getDislikePercentage()) + : formatDislikeCount(voteData.getDislikeCount())); } - private static Spannable newSpanUsingStylingOfAnotherSpan(Spannable sourceStyle, String newSpanText) { + private static Spannable newSpanUsingStylingOfAnotherSpan(Spanned sourceStyle, String newSpanText) { SpannableString destination = new SpannableString(newSpanText); Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class); for (Object span : spans) { diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/RYDVoteData.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/RYDVoteData.java index faaf85a4..969063e8 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/RYDVoteData.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/RYDVoteData.java @@ -1,9 +1,13 @@ package app.revanced.integrations.returnyoutubedislike.requests; +import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote; + +import androidx.annotation.NonNull; + import org.json.JSONException; import org.json.JSONObject; -import java.util.Objects; +import app.revanced.integrations.utils.LogHelper; /** * ReturnYouTubeDislike API estimated like/dislike/view counts. @@ -12,7 +16,7 @@ import java.util.Objects; * So these values may lag behind what YouTube shows. */ public final class RYDVoteData { - + @NonNull public final String videoId; /** @@ -20,46 +24,87 @@ public final class RYDVoteData { */ public final long viewCount; + private final long fetchedLikeCount; + private volatile long likeCount; // read/write from different threads + private volatile float likePercentage; + + private final long fetchedDislikeCount; + private volatile long dislikeCount; // read/write from different threads + private volatile float dislikePercentage; + + /** + * @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"); + fetchedDislikeCount = json.getLong("dislikes"); + if (viewCount < 0 || fetchedLikeCount < 0 || fetchedDislikeCount < 0) { + throw new JSONException("Unexpected JSON values: " + json); + } + likeCount = fetchedLikeCount; + dislikeCount = fetchedDislikeCount; + updatePercentages(); + } + /** * Estimated like count */ - public final long likeCount; + public long getLikeCount() { + return likeCount; + } /** * Estimated dislike count */ - public final long dislikeCount; + public long getDislikeCount() { + return dislikeCount; + } /** * Estimated percentage of likes for all votes. Value has range of [0, 1] * * A video with 400 positive votes, and 100 negative votes, has a likePercentage of 0.8 */ - public final float likePercentage; + public float getLikePercentage() { + return likePercentage; + } /** * Estimated percentage of dislikes for all votes. Value has range of [0, 1] * * A video with 400 positive votes, and 100 negative votes, has a dislikePercentage of 0.2 */ - public final float dislikePercentage; - - /** - * @throws JSONException if JSON parse error occurs, or if the values make no sense (ie: negative values) - */ - public RYDVoteData(JSONObject json) throws JSONException { - Objects.requireNonNull(json); - videoId = json.getString("id"); - viewCount = json.getLong("viewCount"); - likeCount = json.getLong("likes"); - dislikeCount = json.getLong("dislikes"); - if (likeCount < 0 || dislikeCount < 0 || viewCount < 0) { - throw new JSONException("Unexpected JSON values: " + json); - } - likePercentage = (likeCount == 0 ? 0 : (float)likeCount / (likeCount + dislikeCount)); - dislikePercentage = (dislikeCount == 0 ? 0 : (float)dislikeCount / (likeCount + dislikeCount)); + public float getDislikePercentage() { + return dislikePercentage; } + public void updateUsingVote(Vote vote) { + if (vote == Vote.LIKE) { + LogHelper.printDebug(() -> "Increasing like count"); + likeCount = fetchedLikeCount + 1; + dislikeCount = fetchedDislikeCount; + } else if (vote == Vote.DISLIKE) { + LogHelper.printDebug(() -> "Increasing dislike count"); + likeCount = fetchedLikeCount; + dislikeCount = fetchedDislikeCount + 1; + } else if (vote == Vote.LIKE_REMOVE) { + LogHelper.printDebug(() -> "Resetting like/dislike to fetched values"); + likeCount = fetchedLikeCount; + dislikeCount = fetchedDislikeCount; + } else { + throw new IllegalStateException(); + } + updatePercentages(); + } + + private void updatePercentages() { + likePercentage = (likeCount == 0 ? 0 : (float) likeCount / (likeCount + dislikeCount)); + dislikePercentage = (dislikeCount == 0 ? 0 : (float) dislikeCount / (likeCount + dislikeCount)); + } + + @NonNull @Override public String toString() { return "RYDVoteData{" @@ -73,4 +118,5 @@ public final class RYDVoteData { } // equals and hashcode is not implemented (currently not needed) + } diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java index 7729c653..e7c7145e 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java @@ -1,29 +1,27 @@ package app.revanced.integrations.returnyoutubedislike.requests; -import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute; -import static app.revanced.integrations.sponsorblock.StringRef.str; - import android.util.Base64; import android.widget.Toast; - import androidx.annotation.Nullable; - +import app.revanced.integrations.requests.Requester; +import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; import org.json.JSONException; import org.json.JSONObject; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.ProtocolException; +import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Objects; -import app.revanced.integrations.requests.Requester; -import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; +import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute; +import static app.revanced.integrations.sponsorblock.StringRef.str; public class ReturnYouTubeDislikeApi { /** @@ -48,7 +46,13 @@ public class ReturnYouTubeDislikeApi { /** * Response code of a successful API call */ - private static final int SUCCESS_HTTP_STATUS_CODE = 200; + private static final int HTTP_STATUS_CODE_SUCCESS = 200; + + /** + * Response code indicating the video id is not for a video that can be voted for. + * (it's not a Short or a regular video, and it's likely a YouTube Story) + */ + private static final int HTTP_STATUS_CODE_NOT_FOUND = 404; /** * Indicates a client rate limit has been reached @@ -57,9 +61,9 @@ public class ReturnYouTubeDislikeApi { /** * How long to wait until API calls are resumed, if a rate limit is hit. - * No clear guideline of how long to backoff. Using 60 seconds for now. + * No clear guideline of how long to backoff. Using 2 minutes for now. */ - private static final int RATE_LIMIT_BACKOFF_SECONDS = 60; + private static final int RATE_LIMIT_BACKOFF_SECONDS = 120; /** * Last time a {@link #RATE_LIMIT_HTTP_STATUS_CODE} was reached. @@ -133,6 +137,7 @@ public class ReturnYouTubeDislikeApi { * * @param maximumTimeToWait maximum time to wait */ + @SuppressWarnings("UnusedReturnValue") private static long randomlyWaitIfLocallyDebugging(long maximumTimeToWait) { final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false; // set true to debug UI if (DEBUG_RANDOMLY_DELAY_NETWORK_CALLS) { @@ -183,6 +188,8 @@ public class ReturnYouTubeDislikeApi { if (httpResponseCode == RATE_LIMIT_HTTP_STATUS_CODE) { lastTimeRateLimitWasHit = System.currentTimeMillis(); + //noinspection NonAtomicOperationOnVolatileField // don't care, field is used only as an estimate + numberOfRateLimitRequestsEncountered++; LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next " + RATE_LIMIT_BACKOFF_SECONDS + " seconds"); ReVancedUtils.runOnMainThread(() -> { // must show toasts on main thread @@ -208,7 +215,6 @@ public class ReturnYouTubeDislikeApi { fetchCallNumberOfFailures++; } else if (rateLimitHit) { fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT; - numberOfRateLimitRequestsEncountered++; } else { fetchCallResponseTimeLast = responseTimeOfFetchCall; } @@ -228,7 +234,6 @@ public class ReturnYouTubeDislikeApi { LogHelper.printDebug(() -> "Fetching votes for: " + videoId); final long timeNetworkCallStarted = System.currentTimeMillis(); - String connectionErrorMessageStringKey = "revanced_ryd_failure_connection_timeout"; try { HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_DISLIKES, videoId); // request headers, as per https://returnyoutubedislike.com/docs/fetching @@ -250,7 +255,7 @@ public class ReturnYouTubeDislikeApi { return null; } - if (responseCode == SUCCESS_HTTP_STATUS_CODE) { + if (responseCode == HTTP_STATUS_CODE_SUCCESS) { final long timeNetworkCallEnded = System.currentTimeMillis(); // record end time before parsing // do not disconnect, the same server connection will likely be used again soon JSONObject json = Requester.parseJSONObject(connection); @@ -263,13 +268,20 @@ public class ReturnYouTubeDislikeApi { LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex); // fall thru to update statistics } + } else if (responseCode == HTTP_STATUS_CODE_NOT_FOUND) { + // normal response when viewing YouTube Stories (cannot vote for these) + LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId); + return null; // do not updated connection statistics } else { - LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId - + " response code was: " + responseCode, null, str(connectionErrorMessageStringKey)); + LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId + " response code was: " + responseCode, + null, str("revanced_ryd_failure_connection_status_code", responseCode)); connection.disconnect(); // something went wrong, might as well disconnect } - } catch (Exception ex) { // connection timed out, response timeout, or some other network error - LogHelper.printException(() -> "Failed to fetch votes", ex, str(connectionErrorMessageStringKey)); + } catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error + LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_connection_timeout")); + } catch (Exception ex) { + // should never happen + LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage())); } updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(), true, false); @@ -299,7 +311,7 @@ public class ReturnYouTubeDislikeApi { connection.disconnect(); // disconnect, as no more connections will be made for a little while return null; } - if (responseCode == SUCCESS_HTTP_STATUS_CODE) { + if (responseCode == HTTP_STATUS_CODE_SUCCESS) { JSONObject json = Requester.parseJSONObject(connection); String challenge = json.getString("challenge"); int difficulty = json.getInt("difficulty"); @@ -340,7 +352,7 @@ public class ReturnYouTubeDislikeApi { connection.disconnect(); // disconnect, as no more connections will be made for a little while return null; } - if (responseCode == SUCCESS_HTTP_STATUS_CODE) { + if (responseCode == HTTP_STATUS_CODE_SUCCESS) { String result = Requester.parseJson(connection); if (result.equalsIgnoreCase("true")) { LogHelper.printDebug(() -> "Registration confirmation successful for user: " + userId); @@ -387,7 +399,7 @@ public class ReturnYouTubeDislikeApi { connection.disconnect(); // disconnect, as no more connections will be made for a little while return false; } - if (responseCode == SUCCESS_HTTP_STATUS_CODE) { + if (responseCode == HTTP_STATUS_CODE_SUCCESS) { JSONObject json = Requester.parseJSONObject(connection); String challenge = json.getString("challenge"); int difficulty = json.getInt("difficulty"); @@ -431,7 +443,7 @@ public class ReturnYouTubeDislikeApi { return false; } - if (responseCode == SUCCESS_HTTP_STATUS_CODE) { + if (responseCode == HTTP_STATUS_CODE_SUCCESS) { String result = Requester.parseJson(connection); if (result.equalsIgnoreCase("true")) { LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId); @@ -469,9 +481,7 @@ public class ReturnYouTubeDislikeApi { byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP); byte[] buffer = new byte[20]; - for (int i = 4; i < 20; i++) { // FIXME replace with System.arrayCopy - buffer[i] = decodedChallenge[i - 4]; - } + System.arraycopy(decodedChallenge, 0, buffer, 4, 16); MessageDigest md; try { @@ -513,9 +523,9 @@ public class ReturnYouTubeDislikeApi { private static int countLeadingZeroes(byte[] uInt8View) { int zeroes = 0; - int value = 0; - for (int i = 0; i < uInt8View.length; i++) { - value = uInt8View[i] & 0xFF; + int value; + for (byte b : uInt8View) { + value = b & 0xFF; if (value == 0) { zeroes += 8; } else { diff --git a/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt index 490f14d1..d973af2f 100644 --- a/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt +++ b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt @@ -7,8 +7,8 @@ import app.revanced.integrations.utils.Event */ @Suppress("unused") enum class PlayerType { - NONE, // this also includes when shorts are playing - HIDDEN, + NONE, // includes Shorts playback + HIDDEN, // also includes YouTube Shorts and Stories, if a regular video is minimized and a Short/Story is then opened WATCH_WHILE_MINIMIZED, WATCH_WHILE_MAXIMIZED, WATCH_WHILE_FULLSCREEN, @@ -42,6 +42,7 @@ enum class PlayerType { currentPlayerType = value onChange(currentPlayerType) } + @Volatile // value is read/write from different threads private var currentPlayerType = NONE /** @@ -51,7 +52,9 @@ enum class PlayerType { } /** - * Weather Shorts are being played. + * Check if the current player type is [NONE] or [HIDDEN] + * + * @return True, if nothing, a Short, or a Story is playing. */ fun isNoneOrHidden(): Boolean { return this == NONE || this == HIDDEN From c3364226b8814f83cdc55722a08ff91b36470045 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 24 Feb 2023 02:01:49 +0000 Subject: [PATCH 06/10] chore(release): 0.99.0-dev.3 [skip ci] # [0.99.0-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.2...v0.99.0-dev.3) (2023-02-24) ### Features * **youtube/return-youtube-dislike:** support for shorts ([#312](https://github.com/revanced/revanced-integrations/issues/312)) ([0aef5e6](https://github.com/revanced/revanced-integrations/commit/0aef5e60e280b63490dac8d1b706e896fdf913a2)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb112fcf..1c1a0a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.99.0-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.2...v0.99.0-dev.3) (2023-02-24) + + +### Features + +* **youtube/return-youtube-dislike:** support for shorts ([#312](https://github.com/revanced/revanced-integrations/issues/312)) ([0aef5e6](https://github.com/revanced/revanced-integrations/commit/0aef5e60e280b63490dac8d1b706e896fdf913a2)) + # [0.99.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.1...v0.99.0-dev.2) (2023-02-23) diff --git a/gradle.properties b/gradle.properties index 18d18e1c..310546b2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs = -Xmx2048m android.useAndroidX = true -version = 0.99.0-dev.2 +version = 0.99.0-dev.3 From 05bfc689078beb9a21adc6c4555afe0862e304f7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 24 Feb 2023 03:20:44 +0100 Subject: [PATCH 07/10] fix(youtube/general-ads): use correct setting to hide related videos in quick actions Signed-off-by: oSumAtrIX --- .../java/app/revanced/integrations/patches/GeneralAdsPatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java index 44bf0713..27a59ad5 100644 --- a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java @@ -40,7 +40,7 @@ public final class GeneralAdsPatch extends Filter { var webLinkPanel = new BlockRule(SettingsEnum.ADREMOVER_WEB_SEARCH_RESULTS, "web_link_panel"); var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf"); var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar"); - var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "fullscreen_related_videos"); + var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_RELATED_VIDEOS, "fullscreen_related_videos"); var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR, "cell_divider" // layout residue (gray line above the buttoned ad), ); From 476902e9cedbc068a815897dd22eabdb52dee84a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 24 Feb 2023 03:22:17 +0100 Subject: [PATCH 08/10] fix(youtube/general-ads): check for quick actions in path instead of component identifier Signed-off-by: oSumAtrIX --- .../app/revanced/integrations/patches/GeneralAdsPatch.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java index 27a59ad5..a174afe2 100644 --- a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java @@ -41,6 +41,7 @@ public final class GeneralAdsPatch extends Filter { var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf"); var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar"); var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_RELATED_VIDEOS, "fullscreen_related_videos"); + var quickActions = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "quick_actions"); var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR, "cell_divider" // layout residue (gray line above the buttoned ad), ); @@ -79,6 +80,7 @@ public final class GeneralAdsPatch extends Filter { movieAds, chapterTeaser, communityGuidelines, + quickActions, relatedVideos, compactBanner, inFeedSurvey, @@ -95,7 +97,6 @@ public final class GeneralAdsPatch extends Filter { channelMemberShelf ); - var quickActions = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "quick_actions"); var carouselAd = new BlockRule(SettingsEnum.ADREMOVER_GENERAL_ADS_REMOVAL, "carousel_ad" ); @@ -109,7 +110,6 @@ public final class GeneralAdsPatch extends Filter { this.identifierRegister.registerAll( shorts, graySeparator, - quickActions, carouselAd ); } From e626bd08c1249cb5594d15c77d06cae8ae27e055 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 24 Feb 2023 03:24:29 +0100 Subject: [PATCH 09/10] fix(youtube/general-ads): use correct setting to hide related videos in quick actions Signed-off-by: oSumAtrIX --- .../java/app/revanced/integrations/patches/GeneralAdsPatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java index a174afe2..6abb1a6e 100644 --- a/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/GeneralAdsPatch.java @@ -41,7 +41,7 @@ public final class GeneralAdsPatch extends Filter { var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf"); var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar"); var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_RELATED_VIDEOS, "fullscreen_related_videos"); - var quickActions = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "quick_actions"); + var quickActions = new BlockRule(SettingsEnum.ADREMOVER_QUICK_ACTIONS, "quick_actions"); var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR, "cell_divider" // layout residue (gray line above the buttoned ad), ); From 10fff6a0b80dffdea57deea281dabeaf3dcf23bd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 24 Feb 2023 02:30:21 +0000 Subject: [PATCH 10/10] chore(release): 0.99.0-dev.4 [skip ci] # [0.99.0-dev.4](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.3...v0.99.0-dev.4) (2023-02-24) ### Bug Fixes * **youtube/general-ads:** check for quick actions in path instead of component identifier ([476902e](https://github.com/revanced/revanced-integrations/commit/476902e9cedbc068a815897dd22eabdb52dee84a)) * **youtube/general-ads:** use correct setting to hide related videos in quick actions ([e626bd0](https://github.com/revanced/revanced-integrations/commit/e626bd08c1249cb5594d15c77d06cae8ae27e055)) * **youtube/general-ads:** use correct setting to hide related videos in quick actions ([05bfc68](https://github.com/revanced/revanced-integrations/commit/05bfc689078beb9a21adc6c4555afe0862e304f7)) --- CHANGELOG.md | 9 +++++++++ gradle.properties | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1a0a73..d92d7857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.99.0-dev.4](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.3...v0.99.0-dev.4) (2023-02-24) + + +### Bug Fixes + +* **youtube/general-ads:** check for quick actions in path instead of component identifier ([476902e](https://github.com/revanced/revanced-integrations/commit/476902e9cedbc068a815897dd22eabdb52dee84a)) +* **youtube/general-ads:** use correct setting to hide related videos in quick actions ([e626bd0](https://github.com/revanced/revanced-integrations/commit/e626bd08c1249cb5594d15c77d06cae8ae27e055)) +* **youtube/general-ads:** use correct setting to hide related videos in quick actions ([05bfc68](https://github.com/revanced/revanced-integrations/commit/05bfc689078beb9a21adc6c4555afe0862e304f7)) + # [0.99.0-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.2...v0.99.0-dev.3) (2023-02-24) diff --git a/gradle.properties b/gradle.properties index 310546b2..fe684875 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs = -Xmx2048m android.useAndroidX = true -version = 0.99.0-dev.3 +version = 0.99.0-dev.4