From 8e5ca65286b8b62eaeff3bce5fa1d2fb5a198703 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:34:42 +0400 Subject: [PATCH] feat(YouTube - Hide video action buttons): Hide individual action buttons (#451) --- .../patches/components/AdsFilter.java | 4 +- .../patches/components/ButtonsFilter.java | 82 ++++++++++++++---- .../components/LayoutComponentsFilter.java | 4 +- .../patches/components/LithoFilterPatch.java | 84 +++++++++++-------- .../PlaybackSpeedMenuFilterPatch.java | 2 +- .../PlayerFlyoutMenuItemsFilter.java | 4 +- .../patches/components/ShortsFilter.java | 2 +- .../VideoQualityMenuFilterPatch.java | 2 +- .../integrations/settings/SettingsEnum.java | 9 +- 9 files changed, 133 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java index 3797f53a..9ccef8a5 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java @@ -99,12 +99,12 @@ public final class AdsFilter extends Filter { } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (exceptions.matches(path)) return false; - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } /** diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java index 5722e640..8c8e4546 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java @@ -1,59 +1,113 @@ package app.revanced.integrations.patches.components; +import android.os.Build; + import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import app.revanced.integrations.settings.SettingsEnum; +@RequiresApi(api = Build.VERSION_CODES.N) final class ButtonsFilter extends Filter { + + private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml"; + private final StringFilterGroup actionBarRule; + private final StringFilterGroup bufferFilterPathRule; + private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList(); public ButtonsFilter() { actionBarRule = new StringFilterGroup( null, - "video_action_bar" + VIDEO_ACTION_BAR_PATH ); + identifierFilterGroups.addAll(actionBarRule); + + bufferFilterPathRule = new StringFilterGroup( + null, + "|CellType|CollectionType|CellType|ContainerType|button.eml|" + ); pathFilterGroups.addAll( new StringFilterGroup( SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON, - "|like_button", - "dislike_button" + "|segmented_like_dislike_button" ), new StringFilterGroup( SettingsEnum.HIDE_DOWNLOAD_BUTTON, - "download_button" + "|download_button.eml|" ), new StringFilterGroup( SettingsEnum.HIDE_PLAYLIST_BUTTON, - "save_to_playlist_button" + "|save_to_playlist_button" ), new StringFilterGroup( SettingsEnum.HIDE_CLIP_BUTTON, "|clip_button.eml|" ), - new StringFilterGroup( - SettingsEnum.HIDE_ACTION_BUTTONS, - "ContainerType|video_action_button", - "|CellType|CollectionType|CellType|ContainerType|button.eml|" + bufferFilterPathRule + ); + + bufferButtonsGroupList.addAll( + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_LIVE_CHAT_BUTTON, + "yt_outline_message_bubble_overlap" ), - actionBarRule + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_REPORT_BUTTON, + "yt_outline_flag" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_SHARE_BUTTON, + "yt_outline_share" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_REMIX_BUTTON, + "yt_outline_youtube_shorts_plus" + ), + // Check for clip button both here and using a path filter, + // as there's a chance the path is a generic action button and won't contain 'clip_button' + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_CLIP_BUTTON, + "yt_outline_scissors" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_SHOP_BUTTON, + "yt_outline_bag" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_THANKS_BUTTON, + "yt_outline_dollar_sign_heart" + ) ); } private boolean isEveryFilterGroupEnabled() { - for (StringFilterGroup rule : pathFilterGroups) + for (FilterGroup rule : pathFilterGroups) + if (!rule.isEnabled()) return false; + + for (FilterGroup rule : bufferButtonsGroupList) if (!rule.isEnabled()) return false; return true; } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (matchedGroup == actionBarRule) { - return isEveryFilterGroupEnabled(); + if (!isEveryFilterGroupEnabled()) { + return false; + } + } else if (matchedGroup == bufferFilterPathRule) { + if (!path.startsWith(VIDEO_ACTION_BAR_PATH)) { + return false; // Some other unknown button and not part of the player action buttons. + } + if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) { + return false; // Action button is not set to hide. + } } - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java index 53860b9e..534c7a35 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java @@ -172,12 +172,12 @@ public final class LayoutComponentsFilter extends Filter { } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (matchedGroup != custom && exceptions.matches(path)) return false; // Exceptions are not filtered. - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index 6b48a21b..2429fca4 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -1,16 +1,26 @@ package app.revanced.integrations.patches.components; import android.os.Build; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.utils.*; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; import java.util.function.Consumer; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.ByteTrieSearch; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; +import app.revanced.integrations.utils.StringTrieSearch; +import app.revanced.integrations.utils.TrieSearch; + abstract class FilterGroup { final static class FilterGroupResult { SettingsEnum setting; @@ -81,8 +91,7 @@ class StringFilterGroup extends FilterGroup { @Override public FilterGroupResult check(final String string) { - return new FilterGroupResult(setting, - (setting == null || setting.getBoolean()) && ReVancedUtils.containsAny(string, filters)); + return new FilterGroupResult(setting, isEnabled() && ReVancedUtils.containsAny(string, filters)); } } @@ -276,23 +285,12 @@ abstract class Filter { protected final StringFilterGroupList pathFilterGroups = new StringFilterGroupList(); protected final StringFilterGroupList identifierFilterGroups = new StringFilterGroupList(); - /** - * A collection of {@link ByteArrayFilterGroup} that are always searched for (no matter what). - * - * If possible, avoid adding values to this list and instead use a path or identifier filter - * for the item you are looking for. Then inside - * {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}, - * the buffer can then be searched using using a different - * {@link ByteArrayFilterGroupList} or a {@link ByteArrayFilterGroup}. - * This way, the expensive buffer searching only occurs if the cheap and fast path/identifier is already found. - */ - protected final ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList(); /** * Called after an enabled filter has been matched. * Default implementation is to always filter the matched item. * Subclasses can perform additional or different checks if needed. - * + *

* Method is called off the main thread. * * @param matchedList The list the group filter belongs to. @@ -301,15 +299,13 @@ abstract class Filter { * @return True if the litho item should be filtered out. */ @SuppressWarnings("rawtypes") - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (SettingsEnum.DEBUG.getBoolean()) { if (pathFilterGroups == matchedList) { LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path); } else if (identifierFilterGroups == matchedList) { LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier); - } else if (protobufBufferFilterGroups == matchedList) { - LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered from protobuf-buffer"); } } return true; @@ -323,13 +319,14 @@ public final class LithoFilterPatch { * Simple wrapper to pass the litho parameters through the prefix search. */ private static final class LithoFilterParameters { - final String path; + @Nullable final String identifier; + final String path; final byte[] protoBuffer; - LithoFilterParameters(StringBuilder lithoPath, String lithoIdentifier, ByteBuffer protoBuffer) { - this.path = lithoPath.toString(); + LithoFilterParameters(@Nullable String lithoIdentifier, StringBuilder lithoPath, ByteBuffer protoBuffer) { this.identifier = lithoIdentifier; + this.path = lithoPath.toString(); this.protoBuffer = protoBuffer.array(); } @@ -367,7 +364,9 @@ public final class LithoFilterPatch { int value = buffer[end]; if (value < minimumAscii || value > maximumAscii || end == length - 1) { if (end - start >= minimumAsciiStringLength) { - builder.append(new String(buffer, start, end - start)); + for (int i = start; i < end; i++) { + builder.append((char) buffer[i]); + } builder.append(delimitingCharacter); } start = end + 1; @@ -383,22 +382,24 @@ public final class LithoFilterPatch { private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); - private static final ByteTrieSearch protoSearchTree = new ByteTrieSearch(); + + /** + * Because litho filtering is multi-threaded and the buffer is passed in from a different injection point, + * the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads. + */ + private static final ThreadLocal bufferThreadLocal = new ThreadLocal<>(); static { for (Filter filter : filters) { filterGroupLists(pathSearchTree, filter, filter.pathFilterGroups); filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroups); - filterGroupLists(protoSearchTree, filter, filter.protobufBufferFilterGroups); } LogHelper.printDebug(() -> "Using: " + pathSearchTree.numberOfPatterns() + " path filters" + " (" + pathSearchTree.getEstimatedMemorySize() + " KB), " + identifierSearchTree.numberOfPatterns() + " identifier filters" - + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), " - + protoSearchTree.numberOfPatterns() + " buffer filters" - + " (" + protoSearchTree.getEstimatedMemorySize() + " KB)"); + + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB)"); } private static void filterGroupLists(TrieSearch pathSearchTree, @@ -411,7 +412,7 @@ public final class LithoFilterPatch { pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> { if (!group.isEnabled()) return false; LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; - return filter.isFiltered(parameters.path, parameters.identifier, parameters.protoBuffer, + return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer, list, group, matchedStartIndex); } ); @@ -423,23 +424,36 @@ public final class LithoFilterPatch { * Injection point. Called off the main thread. */ @SuppressWarnings("unused") - public static boolean filter(@NonNull StringBuilder pathBuilder, @Nullable String lithoIdentifier, - @NonNull ByteBuffer protobufBuffer) { + public static void setProtoBuffer(@NonNull ByteBuffer protobufBuffer) { + bufferThreadLocal.set(protobufBuffer); + } + + /** + * Injection point. Called off the main thread, and commonly called by multiple threads at the same time. + */ + @SuppressWarnings("unused") + public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) { try { // It is assumed that protobufBuffer is empty as well in this case. if (pathBuilder.length() == 0) return false; - LithoFilterParameters parameter = new LithoFilterParameters(pathBuilder, lithoIdentifier, protobufBuffer); + ByteBuffer protobufBuffer = bufferThreadLocal.get(); + if (protobufBuffer == null) { + LogHelper.printException(() -> "Proto buffer is null"); // Should never happen + return false; + } + LithoFilterParameters parameter = new LithoFilterParameters(lithoIdentifier, pathBuilder, protobufBuffer); LogHelper.printDebug(() -> "Searching " + parameter); - if (pathSearchTree.matches(parameter.path, parameter)) return true; if (parameter.identifier != null) { if (identifierSearchTree.matches(parameter.identifier, parameter)) return true; } - if (protoSearchTree.matches(parameter.protoBuffer, parameter)) return true; + if (pathSearchTree.matches(parameter.path, parameter)) return true; } catch (Exception ex) { LogHelper.printException(() -> "Litho filter failure", ex); + } finally { + bufferThreadLocal.remove(); // Cleanup and remove the buffer. } return false; diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java index ab2e63b4..2e93574f 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java @@ -15,7 +15,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter { } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { isPlaybackSpeedMenuVisible = true; diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java index 3f435e08..9b627f97 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -62,12 +62,12 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { // Only 1 group is added to the parent class, so the matched group must be the overflow menu. if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) { // Super class handles logging. - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } return false; } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java index 48542a06..fa97f2dd 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java @@ -64,7 +64,7 @@ public final class ShortsFilter extends Filter { } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) return true; diff --git a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java index fb94de05..6199effa 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java @@ -17,7 +17,7 @@ public final class VideoQualityMenuFilterPatch extends Filter { } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { isVideoQualityMenuVisible = true; 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 714ae162..9c1a5958 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -89,10 +89,15 @@ public enum SettingsEnum { // Action buttons HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE), + HIDE_LIVE_CHAT_BUTTON("revanced_hide_live_chat_button", BOOLEAN, FALSE), + HIDE_SHARE_BUTTON("revanced_hide_share_button", BOOLEAN, FALSE), + HIDE_REPORT_BUTTON("revanced_hide_report_button", BOOLEAN, FALSE), + HIDE_REMIX_BUTTON("revanced_hide_remix_button", BOOLEAN, TRUE), HIDE_DOWNLOAD_BUTTON("revanced_hide_download_button", BOOLEAN, FALSE), + HIDE_THANKS_BUTTON("revanced_hide_thanks_button", BOOLEAN, TRUE), + HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, TRUE), HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE), - HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, FALSE, "revanced_hide_clip_button_user_dialog_message"), - HIDE_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE), + HIDE_SHOP_BUTTON("revanced_hide_shop_button", BOOLEAN, TRUE), // Layout DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),