revanced-integrations/app/src/main/java/app/revanced/integrations/youtube/patches/components/LayoutComponentsFilter.java

393 lines
14 KiB
Java

package app.revanced.integrations.youtube.patches.components;
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
import android.os.Build;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.shared.Utils;
import app.revanced.integrations.youtube.StringTrieSearch;
import app.revanced.integrations.youtube.settings.Settings;
import app.revanced.integrations.youtube.shared.NavigationBar;
import app.revanced.integrations.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public final class LayoutComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup(
null,
"cell_description_body"
);
private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup(
Settings.HIDE_MIX_PLAYLISTS,
"&list="
);
private final StringFilterGroup searchResultShelfHeader;
private final StringFilterGroup inFeedSurvey;
private final StringFilterGroup notifyMe;
private final StringFilterGroup expandableMetadata;
private final ByteArrayFilterGroup searchResultRecommendations;
private final StringFilterGroup searchResultVideo;
private final StringFilterGroup compactChannelBarInner;
private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton;
private final StringFilterGroup horizontalShelves;
static {
mixPlaylistsExceptions.addPatterns(
"V.ED", // Playlist browse id.
"java.lang.ref.WeakReference"
);
}
@RequiresApi(api = Build.VERSION_CODES.N)
public LayoutComponentsFilter() {
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(
Settings.HIDE_GRAY_SEPARATOR,
"cell_divider" // layout residue (gray line above the buttoned ad),
);
final var chipsShelf = new StringFilterGroup(
Settings.HIDE_CHIPS_SHELF,
"chips_shelf"
);
addIdentifierCallbacks(
graySeparator,
chipsShelf
);
// Paths.
final var communityPosts = new StringFilterGroup(
Settings.HIDE_COMMUNITY_POSTS,
"post_base_wrapper",
"image_post_root.eml"
);
final var communityGuidelines = new StringFilterGroup(
Settings.HIDE_COMMUNITY_GUIDELINES,
"community_guidelines"
);
final var subscribersCommunityGuidelines = new StringFilterGroup(
Settings.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES,
"sponsorships_comments_upsell"
);
final var channelMemberShelf = new StringFilterGroup(
Settings.HIDE_CHANNEL_MEMBER_SHELF,
"member_recognition_shelf"
);
final var compactBanner = new StringFilterGroup(
Settings.HIDE_COMPACT_BANNER,
"compact_banner"
);
inFeedSurvey = new StringFilterGroup(
Settings.HIDE_FEED_SURVEY,
"in_feed_survey",
"slimline_survey"
);
final var medicalPanel = new StringFilterGroup(
Settings.HIDE_MEDICAL_PANELS,
"medical_panel"
);
final var paidPromotion = new StringFilterGroup(
Settings.HIDE_PAID_PROMOTION_LABEL,
"paid_content_overlay"
);
final var infoPanel = new StringFilterGroup(
Settings.HIDE_HIDE_INFO_PANELS,
"publisher_transparency_panel",
"single_item_information_panel"
);
final var latestPosts = new StringFilterGroup(
Settings.HIDE_HIDE_LATEST_POSTS,
"post_shelf"
);
final var channelGuidelines = new StringFilterGroup(
Settings.HIDE_HIDE_CHANNEL_GUIDELINES,
"channel_guidelines_entry_banner"
);
final var emergencyBox = new StringFilterGroup(
Settings.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.
// But it was decided it's simpler to always hide this button because:
// - it doesn't work with copy video url feature
// - the button is rare
// - always hiding makes the ReVanced settings simpler and easier to understand
// - nobody is going to notice the redundant button is always hidden
final var audioTrackButton = new StringFilterGroup(
null,
"multi_feed_icon_button"
);
final var artistCard = new StringFilterGroup(
Settings.HIDE_ARTIST_CARDS,
"official_card"
);
expandableMetadata = new StringFilterGroup(
Settings.HIDE_EXPANDABLE_CHIP,
"inline_expander"
);
final var videoQualityMenuFooter = new StringFilterGroup(
Settings.HIDE_VIDEO_QUALITY_MENU_FOOTER,
"quality_sheet_footer"
);
final var channelBar = new StringFilterGroup(
Settings.HIDE_CHANNEL_BAR,
"channel_bar"
);
final var relatedVideos = new StringFilterGroup(
Settings.HIDE_RELATED_VIDEOS,
"fullscreen_related_videos"
);
final var playables = new StringFilterGroup(
Settings.HIDE_PLAYABLES,
"horizontal_gaming_shelf.eml"
);
final var quickActions = new StringFilterGroup(
Settings.HIDE_QUICK_ACTIONS,
"quick_actions"
);
final var imageShelf = new StringFilterGroup(
Settings.HIDE_IMAGE_SHELF,
"image_shelf"
);
final var timedReactions = new StringFilterGroup(
Settings.HIDE_TIMED_REACTIONS,
"emoji_control_panel",
"timed_reaction"
);
searchResultShelfHeader = new StringFilterGroup(
Settings.HIDE_SEARCH_RESULT_SHELF_HEADER,
"shelf_header.eml"
);
notifyMe = new StringFilterGroup(
Settings.HIDE_NOTIFY_ME_BUTTON,
"set_reminder_button"
);
compactChannelBarInner = new StringFilterGroup(
Settings.HIDE_JOIN_MEMBERSHIP_BUTTON,
"compact_channel_bar_inner"
);
compactChannelBarInnerButton = new StringFilterGroup(
null,
"|button.eml|"
);
joinMembershipButton = new ByteArrayFilterGroup(
null,
"sponsorships"
);
final var channelWatermark = new StringFilterGroup(
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
"featured_channel_watermark_overlay"
);
final var forYouShelf = new StringFilterGroup(
Settings.HIDE_FOR_YOU_SHELF,
"mixed_content_shelf"
);
searchResultVideo = new StringFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
"search_video_with_context.eml"
);
searchResultRecommendations = new ByteArrayFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
"endorsement_header_footer"
);
horizontalShelves = new StringFilterGroup(
Settings.HIDE_HORIZONTAL_SHELVES,
"horizontal_video_shelf.eml",
"horizontal_shelf.eml",
"horizontal_tile_shelf.eml"
);
addPathCallbacks(
expandableMetadata,
inFeedSurvey,
notifyMe,
channelBar,
communityPosts,
paidPromotion,
searchResultVideo,
latestPosts,
channelWatermark,
communityGuidelines,
playables,
quickActions,
relatedVideos,
compactBanner,
compactChannelBarInner,
medicalPanel,
videoQualityMenuFooter,
infoPanel,
emergencyBox,
subscribersCommunityGuidelines,
channelGuidelines,
audioTrackButton,
artistCard,
timedReactions,
imageShelf,
channelMemberShelf,
forYouShelf,
horizontalShelves
);
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == searchResultVideo) {
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
// 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, matchedGroup, contentType, contentIndex);
}
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
if (matchedGroup == compactChannelBarInner) {
if (compactChannelBarInnerButton.check(path).isFiltered()) {
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
// it's safe to assume that the button is the only thing that should be hidden.
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}
return false;
}
// TODO: This also hides the feed Shorts shelf header
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
if (matchedGroup == horizontalShelves) {
if (contentIndex == 0 && hideShelves()) {
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
/**
* Injection point.
* Called from a different place then the other filters.
*/
public static boolean filterMixPlaylists(final Object conversionContext, @Nullable final byte[] bytes) {
if (bytes == null) {
Logger.printDebug(() -> "bytes is null");
return false;
}
// Prevent playlist items being hidden, if a mix playlist is present in it.
if (mixPlaylistsExceptions.matches(conversionContext.toString()))
return false;
if (!mixPlaylists.check(bytes).isFiltered())
return false;
// Prevent hiding the description of some videos accidentally.
if (mixPlaylistsExceptions2.check(bytes).isFiltered())
return false;
Logger.printDebug(() -> "Filtered mix playlist");
return true;
}
/**
* Injection point.
*/
public static boolean showWatermark() {
return !Settings.HIDE_VIDEO_CHANNEL_WATERMARK.get();
}
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
/**
* Injection point.
*/
public static void hideShowMoreButton(View view) {
if (HIDE_SHOW_MORE_BUTTON_ENABLED
&& NavigationBar.isSearchBarActive()
// Search bar can be active but behind the player.
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
Utils.hideViewByLayoutParams(view);
}
}
private static boolean hideShelves() {
// If the player is opened while library is selected,
// then filter any recommendations below the player.
if (PlayerType.getCurrent().isMaximizedOrFullscreen()
// Or if the search is active while library is selected, then also filter.
|| NavigationBar.isSearchBarActive()) {
return true;
}
// Check navigation button last.
// Only filter if the library tab is not selected.
// This check is important as the shelf layout is used for the library tab playlists.
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
return selectedNavButton != null && !selectedNavButton.isLibraryOrYouTab();
}
}