diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e14d042..7a2d5f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,73 @@ +# [1.1.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.6...v1.1.0-dev.7) (2023-12-27) + + +### Bug Fixes + +* **YouTube - Hide layout components:** Hide emergency box when enabled ([6ca7946](https://github.com/ReVanced/revanced-integrations/commit/6ca7946e8f3d5be76241b88f1d7a5a881629dda9)) + + +### Features + +* **YouTube:** Add `Remove viewer discretion dialog` patch ([6f3f882](https://github.com/ReVanced/revanced-integrations/commit/6f3f88264e736b80f88103e795534f86f053c8d1)) + +# [1.1.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.5...v1.1.0-dev.6) (2023-12-26) + + +### Bug Fixes + +* **YouTube - VideoInformation:** Ignore video seek attempts during the last 250ms of video playback ([6263edc](https://github.com/ReVanced/revanced-integrations/commit/6263edce11077f9e9c0629d9260e2f2eaef1c0e8)) + +# [1.1.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.4...v1.1.0-dev.5) (2023-12-25) + + +### Bug Fixes + +* **YouTube - Hide ads:** Hide new type of ad ([#545](https://github.com/ReVanced/revanced-integrations/issues/545)) ([e28b8cc](https://github.com/ReVanced/revanced-integrations/commit/e28b8cc59a445ba8f184ba6f5b9e076b7731129a)) +* **YouTube - Hide ads:** Use correct filter ([4397eed](https://github.com/ReVanced/revanced-integrations/commit/4397eedeeb21edda235cd27cd66088e94bde49e4)) + +# [1.1.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.3...v1.1.0-dev.4) (2023-12-25) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Do not auto skip end segments more than once if using a slow playback speed ([88b3ca4](https://github.com/ReVanced/revanced-integrations/commit/88b3ca4992e8278e1d43dbe5cc7607d4890c0eda)) + +# [1.1.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.2...v1.1.0-dev.3) (2023-12-24) + + +### Features + +* **YouTube - Hide ads:** Hide fullscreen ads ([0f6dee5](https://github.com/ReVanced/revanced-integrations/commit/0f6dee5bae6b8017a53830587e09079942bc24aa)) +* **YouTube - Hide layout components:** Hide search result recommendations ([d241e43](https://github.com/ReVanced/revanced-integrations/commit/d241e437ee25cc2211bf06b4a7f8fd1c295fad25)) + +# [1.1.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.1.0-dev.1...v1.1.0-dev.2) (2023-12-21) + + +### Bug Fixes + +* **Tiktok - Remember clear display:** Use correct name ([2003b91](https://github.com/ReVanced/revanced-integrations/commit/2003b910b12cba445822bfaede7975e00220a81a)) + +# [1.1.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.1-dev.2...v1.1.0-dev.1) (2023-12-21) + + +### Features + +* **Tiktok:** Add `Remember clear mode` patch ([#544](https://github.com/ReVanced/revanced-integrations/issues/544)) ([05eddb6](https://github.com/ReVanced/revanced-integrations/commit/05eddb68d5d5de1b76545c42313d4e9f9ba6712e)) + +## [1.0.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.0.1-dev.1...v1.0.1-dev.2) (2023-12-17) + + +### Bug Fixes + +* **YouTube - Return YouTube Dislike:** Do not prefetch Shorts shelf items on app startup ([697c2aa](https://github.com/ReVanced/revanced-integrations/commit/697c2aaac68976c985f0d838795b03f284e907e8)) + +## [1.0.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0...v1.0.1-dev.1) (2023-12-16) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Export local statistics with saved settings ([#542](https://github.com/ReVanced/revanced-integrations/issues/542)) ([0ed8e5a](https://github.com/ReVanced/revanced-integrations/commit/0ed8e5a2988c07f3dfbd5dd4e9ef8ed53378fbbe)) + # [1.0.0](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v1.0.0) (2023-12-12) diff --git a/app/src/main/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java b/app/src/main/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java new file mode 100644 index 00000000..c0d013e5 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/RemoveViewerDiscretionDialogPatch.java @@ -0,0 +1,18 @@ +package app.revanced.integrations.patches; + +import android.app.AlertDialog; +import app.revanced.integrations.settings.SettingsEnum; + +public class RemoveViewerDiscretionDialogPatch { + public static void confirmDialog(AlertDialog dialog) { + if (!SettingsEnum.REMOVE_VIEWER_DISCRETION_DIALOG.getBoolean()) { + // Since the patch replaces the AlertDialog#show() method, we need to call the original method here. + dialog.show(); + return; + } + + final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + button.setSoundEffectsEnabled(false); + button.performClick(); + } +} 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 9a14366e..9c8caa45 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -570,7 +570,7 @@ public class ReturnYouTubeDislikePatch { return; } - final boolean videoIdIsShort = VideoInformation.lastVideoIdIsShort(); + final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort(); // Shorts shelf in home and subscription feed causes player response hook to be called, // and the 'is opening/playing' parameter will be false. // This hook will be called again when the Short is actually opened. diff --git a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java index a6ba5649..15c97fb5 100644 --- a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java +++ b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java @@ -32,6 +32,7 @@ public final class VideoInformation { @NonNull private static volatile String playerResponseVideoId = ""; + private static volatile boolean playerResponseVideoIdIsShort; private static volatile boolean videoIdIsShort; /** @@ -82,6 +83,7 @@ public final class VideoInformation { */ public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) { final boolean isShort = playerParametersAreShort(signature); + playerResponseVideoIdIsShort = isShort; if (!isShort || isShortAndOpeningOrPlaying) { if (videoIdIsShort != isShort) { videoIdIsShort = isShort; @@ -155,20 +157,29 @@ public final class VideoInformation { * Caution: If called from a videoTimeHook() callback, * this will cause a recursive call into the same videoTimeHook() callback. * - * @param millisecond The millisecond to seek the video to. + * @param seekTime The seekTime to seek the video to. * @return true if the seek was successful. */ - public static boolean seekTo(final long millisecond) { - final long videoLength = getVideoLength(); - - // Prevent issues such as play/ pause button or autoplay not working. - final long seekToMilliseconds = Math.min(millisecond, VideoInformation.getVideoLength() - 250); - + public static boolean seekTo(final long seekTime) { ReVancedUtils.verifyOnMainThread(); try { - LogHelper.printDebug(() -> "Seeking to " + seekToMilliseconds); + final long videoTime = getVideoTime(); + final long videoLength = getVideoLength(); + + // Prevent issues such as play/ pause button or autoplay not working. + final long adjustedSeekTime = Math.min(seekTime, videoLength - 250); + if (videoTime <= seekTime && videoTime >= adjustedSeekTime) { + // Both the current video time and the seekTo are in the last 250ms of the video. + // Ignore this seek call, otherwise if a video ends with multiple closely timed segments + // then seeking here can create an infinite loop of skip attempts. + LogHelper.printDebug(() -> "Ignoring seekTo call as video playback is almost finished. " + + " videoTime: " + videoTime + " videoLength: " + videoLength + " seekTo: " + seekTime); + return false; + } + + LogHelper.printDebug(() -> "Seeking to " + adjustedSeekTime); //noinspection DataFlowIssue - return (Boolean) seekMethod.invoke(playerControllerRef.get(), seekToMilliseconds); + return (Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime); } catch (Exception ex) { LogHelper.printException(() -> "Failed to seek", ex); return false; @@ -206,11 +217,17 @@ public final class VideoInformation { return playerResponseVideoId; } + /** + * @return If the last player response video id was a Short. + * Includes Shorts shelf items appearing in the feed that are not opened. + * @see #lastVideoIdIsShort() + */ + public static boolean lastPlayerResponseIsShort() { + return playerResponseVideoIdIsShort; + } + /** * @return If the last player response video id _that was opened_ was a Short. - *
- * Note: This value returned may not match the status of {@link #getPlayerResponseVideoId()} - * since that includes player responses for videos not opened. */ public static boolean lastVideoIdIsShort() { return videoIdIsShort; 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 5868596d..3b0cb0ec 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 @@ -1,16 +1,25 @@ package app.revanced.integrations.patches.components; - +import android.app.Instrumentation; +import android.view.KeyEvent; import android.view.View; import androidx.annotation.Nullable; import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; import app.revanced.integrations.utils.StringTrieSearch; - +@SuppressWarnings("unused") public final class AdsFilter extends Filter { + // region Fullscreen ad + private static long lastTimeClosedFullscreenAd = 0; + private static final Instrumentation instrumentation = new Instrumentation(); + private final StringFilterGroup fullscreenAd; + + // endregion + private final StringTrieSearch exceptions = new StringTrieSearch(); private final StringFilterGroup shoppingLinks; @@ -23,6 +32,22 @@ public final class AdsFilter extends Filter { "library_recent_shelf" ); + // Identifiers. + + + final var carouselAd = new StringFilterGroup( + SettingsEnum.HIDE_GENERAL_ADS, + "carousel_ad" + ); + addIdentifierCallbacks(carouselAd); + + // Paths. + + fullscreenAd = new StringFilterGroup( + SettingsEnum.HIDE_FULLSCREEN_ADS, + "_interstitial" + ); + final var buttonedAd = new StringFilterGroup( SettingsEnum.HIDE_BUTTONED_ADS, "_buttoned_layout", @@ -30,7 +55,8 @@ public final class AdsFilter extends Filter { "_ad_with", "text_image_button_group_layout", "video_display_button_group_layout", - "landscape_image_wide_button_layout" + "landscape_image_wide_button_layout", + "video_display_carousel_button_group_layout" ); final var generalAds = new StringFilterGroup( @@ -61,11 +87,6 @@ public final class AdsFilter extends Filter { "offer_module_root" ); - final var carouselAd = new StringFilterGroup( - SettingsEnum.HIDE_GENERAL_ADS, - "carousel_ad" - ); - final var viewProducts = new StringFilterGroup( SettingsEnum.HIDE_PRODUCTS_BANNER, "product_item", @@ -92,30 +113,34 @@ public final class AdsFilter extends Filter { "cta_shelf_card" ); - this.pathFilterGroupList.addAll( + addPathCallbacks( generalAds, buttonedAd, merchandise, viewProducts, selfSponsor, + fullscreenAd, webLinkPanel, shoppingLinks, movieAds ); - this.identifierFilterGroupList.addAll(carouselAd); } @Override public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, - FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (exceptions.matches(path)) - return false; - - // Check for the index because of likelihood of false positives. - if (matchedGroup == shoppingLinks && matchedIndex != 0) return false; - return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + if (matchedGroup == fullscreenAd && path.contains("|ImageType|")) { + closeFullscreenAd(); + } + + // Check for the index because of likelihood of false positives. + if (matchedGroup == shoppingLinks && contentIndex != 0) + return false; + + return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); } /** @@ -126,4 +151,21 @@ public final class AdsFilter extends Filter { public static void hideAdAttributionView(View view) { ReVancedUtils.hideViewBy1dpUnderCondition(SettingsEnum.HIDE_GENERAL_ADS, view); } + + /** + * Close the fullscreen ad. + *
+ * The strategy is to send a back button event to the app to close the fullscreen ad using the back button event.
+ */
+ private static void closeFullscreenAd() {
+ final var currentTime = System.currentTimeMillis();
+
+ // Prevent spamming the back button.
+ if (currentTime - lastTimeClosedFullscreenAd < 10000) return;
+ lastTimeClosedFullscreenAd = currentTime;
+
+ LogHelper.printDebug(() -> "Closing fullscreen ad");
+
+ ReVancedUtils.runOnMainThreadDelayed(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK), 1000);
+ }
}
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 19cddf3f..123c5803 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
@@ -7,6 +7,7 @@ import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
+@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
final class ButtonsFilter extends Filter {
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
@@ -20,14 +21,14 @@ final class ButtonsFilter extends Filter {
null,
VIDEO_ACTION_BAR_PATH
);
- identifierFilterGroupList.addAll(actionBarGroup);
+ addIdentifierCallbacks(actionBarGroup);
bufferFilterPathGroup = new StringFilterGroup(
null,
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
);
- pathFilterGroupList.addAll(
+ addPathCallbacks(
new StringFilterGroup(
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
"|segmented_like_dislike_button"
@@ -48,33 +49,33 @@ final class ButtonsFilter extends Filter {
);
bufferButtonsGroupList.addAll(
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_LIVE_CHAT_BUTTON,
"yt_outline_message_bubble_overlap"
),
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_REPORT_BUTTON,
"yt_outline_flag"
),
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHARE_BUTTON,
"yt_outline_share"
),
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
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(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_CLIP_BUTTON,
"yt_outline_scissors"
),
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHOP_BUTTON,
"yt_outline_bag"
),
- new ByteArrayAsStringFilterGroup(
+ new ByteArrayFilterGroup(
SettingsEnum.HIDE_THANKS_BUTTON,
"yt_outline_dollar_sign_heart"
)
@@ -82,7 +83,7 @@ final class ButtonsFilter extends Filter {
}
private boolean isEveryFilterGroupEnabled() {
- for (var group : pathFilterGroupList)
+ for (var group : pathCallbacks)
if (!group.isEnabled()) return false;
for (var group : bufferButtonsGroupList)
@@ -93,7 +94,7 @@ final class ButtonsFilter extends Filter {
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
- FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// If the current matched group is the action bar group,
// in case every filter group is enabled, hide the action bar.
if (matchedGroup == actionBarGroup) {
@@ -109,6 +110,6 @@ final class ButtonsFilter extends Filter {
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
}
- return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
+ return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}
diff --git a/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java
index 089fb948..6ae67f7c 100644
--- a/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java
+++ b/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java
@@ -2,6 +2,7 @@ package app.revanced.integrations.patches.components;
import app.revanced.integrations.settings.SettingsEnum;
+@SuppressWarnings("unused")
final class CommentsFilter extends Filter {
public CommentsFilter() {
@@ -18,7 +19,7 @@ final class CommentsFilter extends Filter {
"comments_entry_point_simplebox"
);
- this.pathFilterGroupList.addAll(
+ addPathCallbacks(
comments,
previewComment
);
diff --git a/app/src/main/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java
index ccdd3210..e786144a 100644
--- a/app/src/main/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java
+++ b/app/src/main/java/app/revanced/integrations/patches/components/DescriptionComponentsFilter.java
@@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.StringTrieSearch;
+@SuppressWarnings("unused")
final class DescriptionComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
@@ -48,7 +49,7 @@ final class DescriptionComponentsFilter extends Filter {
"transcript_section"
);
- pathFilterGroupList.addAll(
+ addPathCallbacks(
chapterSection,
infoCardsSection,
gameSection,
@@ -61,9 +62,9 @@ final class DescriptionComponentsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
- FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (exceptions.matches(path)) return false;
- return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
+ return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/integrations/patches/components/DummyFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/DummyFilter.java
deleted file mode 100644
index 3c36b51a..00000000
--- a/app/src/main/java/app/revanced/integrations/patches/components/DummyFilter.java
+++ /dev/null
@@ -1,3 +0,0 @@
-package app.revanced.integrations.patches.components;
-
-final class DummyFilter extends Filter { }
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java
index fc1f9a1e..b1637b83 100644
--- a/app/src/main/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java
+++ b/app/src/main/java/app/revanced/integrations/patches/components/HideInfoCardsFilterPatch.java
@@ -2,10 +2,11 @@ package app.revanced.integrations.patches.components;
import app.revanced.integrations.settings.SettingsEnum;
+@SuppressWarnings("unused")
public final class HideInfoCardsFilterPatch extends Filter {
public HideInfoCardsFilterPatch() {
- identifierFilterGroupList.addAll(
+ addIdentifierCallbacks(
new StringFilterGroup(
SettingsEnum.HIDE_INFO_CARDS,
"info_card_teaser_overlay.eml"
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 a66bb133..afc9b845 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
@@ -1,24 +1,26 @@
package app.revanced.integrations.patches.components;
-
import android.os.Build;
+
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.StringTrieSearch;
+@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
public final class LayoutComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
- private static final ByteArrayAsStringFilterGroup mixPlaylistsExceptions2 = new ByteArrayAsStringFilterGroup(
+ private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup(
null,
"cell_description_body"
);
private final CustomFilterGroup custom;
- private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
+ private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup(
SettingsEnum.HIDE_MIX_PLAYLISTS,
"&list="
);
@@ -26,6 +28,8 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup inFeedSurvey;
private final StringFilterGroup notifyMe;
private final StringFilterGroup expandableMetadata;
+ private final ByteArrayFilterGroup searchResultRecommendations;
+ private final StringFilterGroup searchResultVideo;
static {
mixPlaylistsExceptions.addPatterns(
@@ -39,11 +43,31 @@ public final class LayoutComponentsFilter extends Filter {
exceptions.addPatterns(
"home_video_with_context",
"related_video_with_context",
+ "search_video_with_context",
"comment_thread", // Whitelist comments
"|comment.", // Whitelist comment replies
"library_recent_shelf"
);
+ // Identifiers.
+
+ final var graySeparator = new StringFilterGroup(
+ SettingsEnum.HIDE_GRAY_SEPARATOR,
+ "cell_divider" // layout residue (gray line above the buttoned ad),
+ );
+
+ final var chipsShelf = new StringFilterGroup(
+ SettingsEnum.HIDE_CHIPS_SHELF,
+ "chips_shelf"
+ );
+
+ addIdentifierCallbacks(
+ graySeparator,
+ chipsShelf
+ );
+
+ // Paths.
+
custom = new CustomFilterGroup(
SettingsEnum.CUSTOM_FILTER,
SettingsEnum.CUSTOM_FILTER_STRINGS
@@ -64,7 +88,6 @@ public final class LayoutComponentsFilter extends Filter {
"sponsorships_comments_upsell"
);
-
final var channelMemberShelf = new StringFilterGroup(
SettingsEnum.HIDE_CHANNEL_MEMBER_SHELF,
"member_recognition_shelf"
@@ -107,6 +130,11 @@ public final class LayoutComponentsFilter extends Filter {
"channel_guidelines_entry_banner"
);
+ final var emergencyBox = new StringFilterGroup(
+ SettingsEnum.HIDE_EMERGENCY_BOX,
+ "emergency_onebox"
+ );
+
// The player audio track button does the exact same function as the audio track flyout menu option.
// But if the copy url button is shown, these button clashes and the the audio button does not work.
// Previously this was a setting to show/hide the player button.
@@ -155,10 +183,6 @@ public final class LayoutComponentsFilter extends Filter {
"image_shelf"
);
- final var graySeparator = new StringFilterGroup(
- SettingsEnum.HIDE_GRAY_SEPARATOR,
- "cell_divider" // layout residue (gray line above the buttoned ad),
- );
final var timedReactions = new StringFilterGroup(
SettingsEnum.HIDE_TIMED_REACTIONS,
@@ -181,11 +205,6 @@ public final class LayoutComponentsFilter extends Filter {
"compact_sponsor_button"
);
- final var chipsShelf = new StringFilterGroup(
- SettingsEnum.HIDE_CHIPS_SHELF,
- "chips_shelf"
- );
-
final var channelWatermark = new StringFilterGroup(
SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK,
"featured_channel_watermark_overlay"
@@ -196,23 +215,36 @@ public final class LayoutComponentsFilter extends Filter {
"mixed_content_shelf"
);
- this.pathFilterGroupList.addAll(
+ searchResultVideo = new StringFilterGroup(
+ SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
+ "search_video_with_context.eml"
+ );
+
+ searchResultRecommendations = new ByteArrayFilterGroup(
+ SettingsEnum.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
+ "endorsement_header_footer"
+ );
+
+ addPathCallbacks(
+ custom,
+ expandableMetadata,
+ inFeedSurvey,
+ notifyMe,
channelBar,
communityPosts,
paidContent,
+ searchResultVideo,
latestPosts,
channelWatermark,
communityGuidelines,
quickActions,
- expandableMetadata,
relatedVideos,
compactBanner,
- inFeedSurvey,
joinMembership,
medicalPanel,
- notifyMe,
videoQualityMenuFooter,
infoPanel,
+ emergencyBox,
subscribersCommunityGuidelines,
channelGuidelines,
audioTrackButton,
@@ -220,32 +252,31 @@ public final class LayoutComponentsFilter extends Filter {
timedReactions,
imageShelf,
channelMemberShelf,
- forYouShelf,
- custom
- );
-
- this.identifierFilterGroupList.addAll(
- graySeparator,
- chipsShelf
+ forYouShelf
);
}
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
- FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
+ if (matchedGroup == searchResultVideo) {
+ if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
+ return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
+ }
+ }
// The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here.
- if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
- return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
+ if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
+ return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
if (matchedGroup != custom && exceptions.matches(path))
return false; // Exceptions are not filtered.
// TODO: This also hides the feed Shorts shelf header
- if (matchedGroup == searchResultShelfHeader && matchedIndex != 0) return false;
+ if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
- return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
+ return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
/**
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 96b0976e..26b31e64 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
@@ -195,6 +195,14 @@ class ByteArrayFilterGroup extends FilterGroup
+ * If the content is to be filtered, subclasses should always
+ * call this method (and never return a plain 'true').
+ * That way the logs will always show when a component was filtered and which filter hide it.
+ *
* Method is called off the main thread.
*
- * @param matchedList The list the group filter belongs to.
* @param matchedGroup The actual filter that matched.
- * @param matchedIndex Matched index of string/array.
- * @return True if the litho item should be filtered out.
+ * @param contentType The type of content matched.
+ * @param contentIndex Matched index of the identifier or path.
+ * @return True if the litho component should be filtered out.
*/
- @SuppressWarnings("rawtypes")
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
- FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (SettingsEnum.DEBUG.getBoolean()) {
- if (matchedList == identifierFilterGroupList) {
- LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
+ String filterSimpleName = getClass().getSimpleName();
+ if (contentType == FilterContentType.IDENTIFIER) {
+ LogHelper.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier);
} else {
- LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
+ LogHelper.printDebug(() -> filterSimpleName + " Filtered path: " + path);
}
}
return true;
}
}
+/**
+ * Placeholder for actual filters.
+ */
+final class DummyFilter extends Filter { }
+
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressWarnings("unused")
public final class LithoFilterPatch {
@@ -437,8 +473,10 @@ public final class LithoFilterPatch {
static {
for (Filter filter : filters) {
- filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList);
- filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
+ filterUsingCallbacks(identifierSearchTree, filter,
+ filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
+ filterUsingCallbacks(pathSearchTree, filter,
+ filter.pathCallbacks, Filter.FilterContentType.PATH);
}
LogHelper.printDebug(() -> "Using: "
@@ -448,18 +486,19 @@ public final class LithoFilterPatch {
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
}
- private static