fix(YouTube - Hide action buttons): Fix empty spaces when hiding buttons
Co-Authored-By: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
parent
34e3d92456
commit
bda1ddba69
7 changed files with 540 additions and 143 deletions
|
|
@ -1,161 +1,324 @@
|
|||
/*
|
||||
* Copyright 2026 Morphe.
|
||||
* https://github.com/MorpheApp/morphe-patches
|
||||
*
|
||||
* Original hard forked code:
|
||||
* https://github.com/ReVanced/revanced-patches/commit/724e6d61b2ecd868c1a9a37d465a688e83a74799
|
||||
*/
|
||||
|
||||
package app.morphe.extension.youtube.patches.components;
|
||||
|
||||
import static app.morphe.extension.youtube.utils.ExtendedUtils.IS_19_26_OR_GREATER;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.protobuf.MessageLite;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import app.morphe.extension.shared.patches.components.ByteArrayFilterGroup;
|
||||
import app.morphe.extension.shared.patches.components.ByteArrayFilterGroupList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import app.morphe.extension.shared.patches.components.Filter;
|
||||
import app.morphe.extension.shared.patches.components.StringFilterGroup;
|
||||
import app.morphe.extension.shared.utils.Logger;
|
||||
import app.morphe.extension.shared.utils.Utils;
|
||||
import app.morphe.extension.youtube.innertube.NextResponseOuterClass.ActionButtons;
|
||||
import app.morphe.extension.youtube.innertube.NextResponseOuterClass.NewElement;
|
||||
import app.morphe.extension.youtube.innertube.NextResponseOuterClass.SecondaryContents;
|
||||
import app.morphe.extension.youtube.innertube.NextResponseOuterClass.SingleColumnWatchNextResults;
|
||||
import app.morphe.extension.youtube.settings.Settings;
|
||||
import app.morphe.extension.youtube.shared.VideoInformation;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
@SuppressWarnings("unused")
|
||||
public final class ActionButtonsFilter extends Filter {
|
||||
public enum ActionButton {
|
||||
UNKNOWN(false),
|
||||
ASK(
|
||||
Settings.HIDE_ASK_BUTTON.get(),
|
||||
"yt_fill_experimental_spark",
|
||||
"yt_fill_spark"
|
||||
),
|
||||
CHANNEL_PROFILE(false),
|
||||
CLIP(Settings.HIDE_CLIP_BUTTON.get()),
|
||||
COMMENTS(
|
||||
Settings.HIDE_COMMENTS_BUTTON.get(),
|
||||
"yt_outline_experimental_text_bubble",
|
||||
"yt_outline_message_bubble",
|
||||
"yt_outline_message_bubble_right"
|
||||
),
|
||||
DOWNLOAD(Settings.HIDE_DOWNLOAD_BUTTON.get()),
|
||||
HYPE(
|
||||
Settings.HIDE_HYPE_BUTTON.get(),
|
||||
"yt_outline_experimental_hype",
|
||||
"yt_outline_star_shooting"
|
||||
),
|
||||
LIKE_DISLIKE(Settings.HIDE_LIKE_DISLIKE_BUTTON.get()),
|
||||
PLAYLIST(Settings.HIDE_PLAYLIST_BUTTON.get()),
|
||||
PROMOTE(
|
||||
Settings.HIDE_PROMOTE_BUTTON.get(),
|
||||
"yt_outline_experimental_megaphone",
|
||||
"yt_outline_megaphone"
|
||||
),
|
||||
REMIX(
|
||||
Settings.HIDE_REMIX_BUTTON.get(),
|
||||
"yt_outline_youtube_shorts_plus",
|
||||
"yt_outline_experimental_remix"
|
||||
),
|
||||
REPORT(
|
||||
Settings.HIDE_REPORT_BUTTON.get(),
|
||||
"yt_outline_experimental_flag",
|
||||
"yt_outline_flag"
|
||||
),
|
||||
REWARDS(
|
||||
Settings.HIDE_REWARDS_BUTTON.get(),
|
||||
"yt_outline_experimental_account_link",
|
||||
"yt_outline_account_link"
|
||||
),
|
||||
SHARE(
|
||||
Settings.HIDE_SHARE_BUTTON.get(),
|
||||
"yt_outline_experimental_share",
|
||||
"yt_outline_share"
|
||||
),
|
||||
SHOP(
|
||||
Settings.HIDE_SHOP_BUTTON.get(),
|
||||
"yt_outline_experimental_bag",
|
||||
"yt_outline_bag"
|
||||
),
|
||||
STOP_ADS(
|
||||
Settings.HIDE_STOP_ADS_BUTTON.get(),
|
||||
"yt_outline_experimental_circle_slash",
|
||||
"yt_outline_slash_circle_left"
|
||||
),
|
||||
THANKS(
|
||||
Settings.HIDE_THANKS_BUTTON.get(),
|
||||
"yt_outline_experimental_dollar_sign_heart",
|
||||
"yt_outline_dollar_sign_heart"
|
||||
);
|
||||
|
||||
public final boolean shouldHide;
|
||||
@NonNull
|
||||
public final List<String> iconNames;
|
||||
|
||||
ActionButton(boolean shouldHide) {
|
||||
this.shouldHide = shouldHide;
|
||||
this.iconNames = Collections.emptyList();
|
||||
}
|
||||
|
||||
ActionButton(boolean shouldHide, @NonNull String... iconNames) {
|
||||
this.shouldHide = shouldHide;
|
||||
this.iconNames = Arrays.asList(iconNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Video bar path when the video information is collapsed. Seems to shown only with 20.14+
|
||||
* Whether to parse watch next results and remove action buttons from the tree node list.
|
||||
*/
|
||||
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.";
|
||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.";
|
||||
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
||||
private static final boolean HIDE_ACTION_BUTTON;
|
||||
|
||||
static {
|
||||
boolean hideActionButton = false;
|
||||
for (ActionButton button : ActionButton.values()) {
|
||||
if (button.shouldHide) {
|
||||
hideActionButton = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
HIDE_ACTION_BUTTON = !Settings.HIDE_ACTION_BUTTON_INDEX.get() && hideActionButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a list of action buttons based on video ID.
|
||||
*/
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, List<ActionButton>> actionButtonLookup =
|
||||
Utils.createSizeRestrictedMap(10);
|
||||
|
||||
private static final String COMPACT_CHANNEL_BAR_PREFIX = "compact_channel_bar.";
|
||||
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PREFIX = "compactify_video_action_bar.";
|
||||
private static final String VIDEO_ACTION_BAR_PREFIX = "video_action_bar.";
|
||||
|
||||
private final StringFilterGroup actionBarRule;
|
||||
private final StringFilterGroup bufferFilterPathRule;
|
||||
private final StringFilterGroup likeSubscribeGlow;
|
||||
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private static final boolean HIDE_ACTION_BUTTON_INDEX = Settings.HIDE_ACTION_BUTTON_INDEX.get();
|
||||
|
||||
public ActionButtonsFilter() {
|
||||
actionBarRule = new StringFilterGroup(
|
||||
null,
|
||||
VIDEO_ACTION_BAR_PATH
|
||||
StringFilterGroup actionBarGroup = new StringFilterGroup(
|
||||
Settings.HIDE_ACTION_BAR,
|
||||
VIDEO_ACTION_BAR_PREFIX
|
||||
);
|
||||
addIdentifierCallbacks(actionBarRule);
|
||||
addIdentifierCallbacks(actionBarGroup);
|
||||
|
||||
bufferFilterPathRule = new StringFilterGroup(
|
||||
null,
|
||||
"|ContainerType|button."
|
||||
);
|
||||
likeSubscribeGlow = new StringFilterGroup(
|
||||
Settings.DISABLE_LIKE_DISLIKE_GLOW,
|
||||
"animated_button_border."
|
||||
);
|
||||
addPathCallbacks(
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
||||
"|segmented_like_dislike_button"
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_DOWNLOAD_BUTTON,
|
||||
"|download_button."
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_CLIP_BUTTON,
|
||||
"|clip_button."
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_PLAYLIST_BUTTON,
|
||||
"|save_to_playlist_button"
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_REWARDS_BUTTON,
|
||||
"account_link_button"
|
||||
),
|
||||
bufferFilterPathRule,
|
||||
likeSubscribeGlow
|
||||
);
|
||||
|
||||
bufferButtonsGroupList.addAll(
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_COMMENTS_BUTTON,
|
||||
"yt_outline_message_bubble"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_REPORT_BUTTON,
|
||||
"yt_outline_flag"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHARE_BUTTON,
|
||||
"yt_outline_share"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_REMIX_BUTTON,
|
||||
"yt_outline_youtube_shorts_plus"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_THANKS_BUTTON,
|
||||
"yt_outline_dollar_sign_heart"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_ASK_BUTTON,
|
||||
"yt_fill_spark"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHOP_BUTTON,
|
||||
"yt_outline_bag"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_STOP_ADS_BUTTON,
|
||||
"yt_outline_slash_circle_left"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_CLIP_BUTTON,
|
||||
"yt_outline_scissors"
|
||||
),
|
||||
// 1. YouTube 19.25.39 can be used without the 'Disable update screen' patch.
|
||||
// This means that even if you use an unpatched YouTube 19.25.39, the 'Update your app' pop-up will not appear.
|
||||
// 2. Due to a server-side change, the Hype button is now available on YouTube 19.25.39 and earlier.
|
||||
// 3. Google did not add the Hype icon (R.drawable.yt_outline_star_shooting_black_24) to YouTube 19.25.39 or earlier,
|
||||
// So no icon appears on the Hype button when using YouTube 19.25.39.
|
||||
// 4. For the same reason, the 'buttonViewModel.iconName' value in the '/next' endpoint response from YouTube 19.25.39 is also empty.
|
||||
// 5. Therefore, in YouTube 19.25.39 or earlier versions, you cannot hide the Hype button with the keyword 'yt_outline_star_shooting',
|
||||
// but you can hide it with the keyword 'Hype'.
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_HYPE_BUTTON,
|
||||
IS_19_26_OR_GREATER || Settings.FIX_HYPE_BUTTON_ICON.get()
|
||||
? "yt_outline_star_shooting"
|
||||
: "Hype"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_PROMOTE_BUTTON,
|
||||
"yt_outline_megaphone"
|
||||
)
|
||||
);
|
||||
addPathCallbacks(likeSubscribeGlow);
|
||||
}
|
||||
|
||||
private boolean isEveryFilterGroupEnabled() {
|
||||
for (StringFilterGroup group : pathCallbacks)
|
||||
if (!group.isEnabled()) return false;
|
||||
private static boolean isVideoActionBar(@NonNull String identifier) {
|
||||
return StringUtils.startsWithAny(identifier, COMPACTIFY_VIDEO_ACTION_BAR_PREFIX, VIDEO_ACTION_BAR_PREFIX);
|
||||
}
|
||||
|
||||
for (ByteArrayFilterGroup group : bufferButtonsGroupList)
|
||||
if (!group.isEnabled()) return false;
|
||||
|
||||
return true;
|
||||
private static ActionButton getActionButton(@NonNull String iconName) {
|
||||
for (ActionButton button : ActionButton.values()) {
|
||||
for (String icon : button.iconNames) {
|
||||
if (iconName.contains(icon)) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ActionButton.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(String path, String identifier, String allValue, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (HIDE_ACTION_BUTTON_INDEX) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.startsWithAny(path, COMPACTIFY_VIDEO_ACTION_BAR_PATH, VIDEO_ACTION_BAR_PATH)) {
|
||||
return false;
|
||||
}
|
||||
if (matchedGroup == actionBarRule && !isEveryFilterGroupEnabled()) {
|
||||
return false;
|
||||
}
|
||||
if (matchedGroup == likeSubscribeGlow) {
|
||||
if (!path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (matchedGroup == bufferFilterPathRule) {
|
||||
// In case the group list has no match, return false.
|
||||
return bufferButtonsGroupList.check(buffer).isFiltered();
|
||||
return StringUtils.startsWithAny(
|
||||
path,
|
||||
COMPACT_CHANNEL_BAR_PREFIX,
|
||||
COMPACTIFY_VIDEO_ACTION_BAR_PREFIX,
|
||||
VIDEO_ACTION_BAR_PREFIX
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Called after {@link #onSingleColumnWatchNextResultsLoaded(MessageLite)}.
|
||||
*/
|
||||
public static void onLazilyConvertedElementLoaded(@NonNull List<Object> treeNodeResultList,
|
||||
@NonNull String identifier) {
|
||||
if (!HIDE_ACTION_BUTTON || !isVideoActionBar(identifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (actionButtonLookup) {
|
||||
String videoId = VideoInformation.getVideoId();
|
||||
List<ActionButton> actionButtons = actionButtonLookup.get(videoId);
|
||||
if (actionButtons == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int actionButtonSize = actionButtons.size();
|
||||
int treeNodeResultListSize = treeNodeResultList.size();
|
||||
if (actionButtonSize != treeNodeResultListSize) {
|
||||
Logger.printDebug(() -> "The sizes of the lists do not match, actionButtonSize: "
|
||||
+ actionButtonSize + ", treeNodeResultListSize: " + treeNodeResultListSize);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = actionButtonSize - 1; i > -1; i--) {
|
||||
ActionButton actionButton = actionButtons.get(i);
|
||||
if (actionButton.shouldHide && i < treeNodeResultListSize) {
|
||||
treeNodeResultList.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Invoke as soon as the endpoint response is received.
|
||||
*/
|
||||
public static void onSingleColumnWatchNextResultsLoaded(@NonNull MessageLite messageLite) {
|
||||
if (!HIDE_ACTION_BUTTON) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (actionButtonLookup) {
|
||||
try {
|
||||
var singleColumnWatchNextResults = SingleColumnWatchNextResults.parseFrom(messageLite.toByteArray());
|
||||
var primaryResults = singleColumnWatchNextResults.getPrimaryResults();
|
||||
var secondaryResults = primaryResults.getSecondaryResults();
|
||||
|
||||
SecondaryContents finalSecondaryContents = null;
|
||||
for (var secondaryContents : secondaryResults.getSecondaryContentsList()) {
|
||||
if (secondaryContents.hasSlimVideoMetadataSectionRenderer()) {
|
||||
finalSecondaryContents = secondaryContents;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (finalSecondaryContents == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var slimVideoMetadataSectionRenderer = finalSecondaryContents.getSlimVideoMetadataSectionRenderer();
|
||||
String videoId = slimVideoMetadataSectionRenderer.getVideoId();
|
||||
if (actionButtonLookup.containsKey(videoId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NewElement finalNewElement = null;
|
||||
for (var tertiaryContents : slimVideoMetadataSectionRenderer.getTertiaryContentsList()) {
|
||||
var newElement = tertiaryContents.getElementRenderer().getNewElement();
|
||||
String identifier = newElement.getProperties().getIdentifierProperties().getIdentifier();
|
||||
if (isVideoActionBar(identifier)) {
|
||||
finalNewElement = newElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (finalNewElement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var model = finalNewElement.getType().getComponentType().getModel();
|
||||
List<ActionButtons> finalActionButtons = null;
|
||||
|
||||
if (model.hasYoutubeModel()) {
|
||||
finalActionButtons = model.getYoutubeModel()
|
||||
.getViewModel()
|
||||
.getCompactifyVideoActionBarViewModel()
|
||||
.getActionButtonsList();
|
||||
} else if (model.hasVideoActionBarModel()) {
|
||||
finalActionButtons = model.getVideoActionBarModel()
|
||||
.getVideoActionBarData()
|
||||
.getActionButtonsList();
|
||||
} else {
|
||||
Logger.printDebug(() -> "Unknown model: " + model + ", videoId: " + videoId);
|
||||
}
|
||||
if (finalActionButtons == null || finalActionButtons.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ActionButton> actionButtons = new ArrayList<>(finalActionButtons.size());
|
||||
for (var buttons : finalActionButtons) {
|
||||
ActionButton actionButton = ActionButton.UNKNOWN;
|
||||
var primaryButtonViewModel = buttons.getPrimaryButtonViewModel();
|
||||
|
||||
if (primaryButtonViewModel.hasSecondaryButtonViewModel()) {
|
||||
String iconName = primaryButtonViewModel.getSecondaryButtonViewModel().getIconName();
|
||||
if (iconName != null) {
|
||||
actionButton = getActionButton(iconName);
|
||||
if (actionButton == ActionButton.UNKNOWN) {
|
||||
Logger.printDebug(() -> "Unknown iconName: " + iconName + ", videoId: " + videoId);
|
||||
}
|
||||
}
|
||||
} else if (primaryButtonViewModel.hasAddToPlaylistButtonViewModel()) {
|
||||
actionButton = ActionButton.PLAYLIST;
|
||||
} else if (primaryButtonViewModel.hasClipButtonViewModel()) {
|
||||
actionButton = ActionButton.CLIP;
|
||||
} else if (primaryButtonViewModel.hasCompactChannelBarViewModel()) {
|
||||
actionButton = ActionButton.CHANNEL_PROFILE;
|
||||
} else if (primaryButtonViewModel.hasDownloadButtonViewModel()) {
|
||||
actionButton = ActionButton.DOWNLOAD;
|
||||
} else if (primaryButtonViewModel.hasSegmentedLikeDislikeButtonViewModel()) {
|
||||
actionButton = ActionButton.LIKE_DISLIKE;
|
||||
} else {
|
||||
Logger.printDebug(() -> "Unknown buttonViewModel: " + primaryButtonViewModel + ", videoId: " + videoId);
|
||||
}
|
||||
|
||||
actionButtons.add(actionButton);
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "New video id: " + videoId + ", action buttons: " + actionButtons);
|
||||
actionButtonLookup.put(videoId, actionButtons);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to parse SingleColumnWatchNextResults", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -327,23 +327,24 @@ public class Settings extends SharedYouTubeSettings {
|
|||
|
||||
// PreferenceScreen: Player - Action buttons
|
||||
public static final BooleanSetting DISABLE_LIKE_DISLIKE_GLOW = new BooleanSetting("revanced_disable_like_dislike_glow", FALSE);
|
||||
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_comments_button", FALSE);
|
||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE);
|
||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||
public static final BooleanSetting HIDE_PLAYLIST_BUTTON = new BooleanSetting("revanced_hide_playlist_button", FALSE);
|
||||
public static final BooleanSetting HIDE_PROMOTE_BUTTON = new BooleanSetting("revanced_hide_promote_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REWARDS_BUTTON = new BooleanSetting("revanced_hide_rewards_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE);
|
||||
public static final BooleanSetting HIDE_STOP_ADS_BUTTON = new BooleanSetting("revanced_hide_stop_ads_button", FALSE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BAR = new BooleanSetting("revanced_hide_action_bar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_comments_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_PLAYLIST_BUTTON = new BooleanSetting("revanced_hide_playlist_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_PROMOTE_BUTTON = new BooleanSetting("revanced_hide_promote_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_REWARDS_BUTTON = new BooleanSetting("revanced_hide_rewards_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_STOP_ADS_BUTTON = new BooleanSetting("revanced_hide_stop_ads_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX = new BooleanSetting("revanced_hide_action_button_index", FALSE, true);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX = new BooleanSetting("revanced_hide_action_button_index", FALSE, true, parentInverted(HIDE_ACTION_BAR));
|
||||
public static final IntegerSetting REMIX_BUTTON_INDEX = new IntegerSetting("revanced_remix_button_index", 3, true, parent(HIDE_ACTION_BUTTON_INDEX));
|
||||
|
||||
// PreferenceScreen: Player - Ambient mode
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package app.morphe.extension.youtube.innertube;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.morphe.extension.youtube.innertube";
|
||||
|
||||
message NextResponse {
|
||||
PrimaryContents primaryContents = 7;
|
||||
}
|
||||
|
||||
message PrimaryContents {
|
||||
SingleColumnWatchNextResults singleColumnWatchNextResults = 51779735;
|
||||
}
|
||||
|
||||
message SingleColumnWatchNextResults {
|
||||
PrimaryResults primaryResults = 1;
|
||||
}
|
||||
|
||||
message PrimaryResults {
|
||||
SecondaryResults secondaryResults = 49399797;
|
||||
}
|
||||
|
||||
message SecondaryResults {
|
||||
repeated SecondaryContents secondaryContents = 1;
|
||||
}
|
||||
|
||||
message SecondaryContents {
|
||||
oneof data {
|
||||
SlimVideoMetadataSectionRenderer slimVideoMetadataSectionRenderer = 216561405;
|
||||
ItemSectionRenderer itemSectionRenderer = 50195462;
|
||||
ShelfRenderer shelfRenderer = 51845067;
|
||||
}
|
||||
}
|
||||
|
||||
message SlimVideoMetadataSectionRenderer {
|
||||
repeated TertiaryContents tertiaryContents = 1;
|
||||
string videoId = 2;
|
||||
}
|
||||
|
||||
message ItemSectionRenderer {
|
||||
repeated TertiaryContents tertiaryContents = 1;
|
||||
string sectionIdentifier = 7;
|
||||
}
|
||||
|
||||
message ShelfRenderer {
|
||||
Content content = 5;
|
||||
}
|
||||
|
||||
message Content {
|
||||
HorizontalListRenderer horizontalListRenderer = 51431404;
|
||||
}
|
||||
|
||||
message HorizontalListRenderer {
|
||||
int32 visibleItemCount = 7;
|
||||
}
|
||||
|
||||
message TertiaryContents {
|
||||
ElementRenderer elementRenderer = 153515154;
|
||||
}
|
||||
|
||||
message ElementRenderer {
|
||||
NewElement newElement = 172660663;
|
||||
}
|
||||
|
||||
message NewElement {
|
||||
Type type = 1;
|
||||
Properties properties = 2;
|
||||
}
|
||||
|
||||
message Properties {
|
||||
IdentifierProperties identifierProperties = 183314536;
|
||||
}
|
||||
|
||||
message IdentifierProperties {
|
||||
string identifier = 1;
|
||||
}
|
||||
|
||||
message Type {
|
||||
ComponentType componentType = 168777401;
|
||||
}
|
||||
|
||||
message ComponentType {
|
||||
Model model = 5;
|
||||
}
|
||||
|
||||
message Model {
|
||||
oneof data {
|
||||
VideoActionBarModel videoActionBarModel = 389155117;
|
||||
VideoMetadataCarouselModel videoMetadataCarouselModel = 408379376;
|
||||
YoutubeModel youtubeModel = 413471385;
|
||||
}
|
||||
}
|
||||
|
||||
message VideoMetadataCarouselModel {
|
||||
Data data = 1;
|
||||
}
|
||||
|
||||
message Data {
|
||||
repeated CarouselTitleDatas carouselTitleDatas = 1;
|
||||
repeated CarouselItemDatas carouselItemDatas = 2;
|
||||
}
|
||||
|
||||
message CarouselTitleDatas {
|
||||
string title = 1;
|
||||
}
|
||||
|
||||
message CarouselItemDatas {
|
||||
}
|
||||
|
||||
message YoutubeModel {
|
||||
ViewModel viewModel = 1;
|
||||
}
|
||||
|
||||
message ViewModel {
|
||||
CompactifyVideoActionBarViewModel compactifyVideoActionBarViewModel = 2185;
|
||||
}
|
||||
|
||||
message CompactifyVideoActionBarViewModel {
|
||||
repeated ActionButtons actionButtons = 1;
|
||||
}
|
||||
|
||||
message VideoActionBarModel {
|
||||
VideoActionBarData videoActionBarData = 17;
|
||||
}
|
||||
|
||||
message VideoActionBarData {
|
||||
repeated ActionButtons actionButtons = 1;
|
||||
}
|
||||
|
||||
message ActionButtons {
|
||||
PrimaryButtonViewModel primaryButtonViewModel = 10;
|
||||
}
|
||||
|
||||
message PrimaryButtonViewModel {
|
||||
oneof data {
|
||||
AddToPlaylistButtonViewModel addToPlaylistButtonViewModel = 484598023;
|
||||
SecondaryButtonViewModel secondaryButtonViewModel = 461054335;
|
||||
ClipButtonViewModel clipButtonViewModel = 490025863;
|
||||
CompactChannelBarViewModel compactChannelBarViewModel = 1107;
|
||||
DownloadButtonViewModel downloadButtonViewModel = 33561776;
|
||||
SegmentedLikeDislikeButtonViewModel segmentedLikeDislikeButtonViewModel = 484586709;
|
||||
}
|
||||
}
|
||||
|
||||
message AddToPlaylistButtonViewModel {
|
||||
string addToPlaylistEntityKey = 2;
|
||||
}
|
||||
|
||||
message SecondaryButtonViewModel {
|
||||
string iconName = 2;
|
||||
optional string accessibilityId = 17;
|
||||
}
|
||||
|
||||
message ClipButtonViewModel {
|
||||
string videoActionButtonEnablementEntityKey = 2;
|
||||
}
|
||||
|
||||
message CompactChannelBarViewModel {
|
||||
bool enableCompactChannelBarInnerRefactor = 29;
|
||||
}
|
||||
|
||||
message DownloadButtonViewModel {
|
||||
string videoId = 1;
|
||||
}
|
||||
|
||||
message SegmentedLikeDislikeButtonViewModel {
|
||||
string teasersOrderEntityKey = 10;
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
package app.morphe.patches.youtube.player.action
|
||||
|
||||
import app.morphe.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.morphe.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.morphe.patcher.patch.bytecodePatch
|
||||
import app.morphe.patches.shared.misc.fix.proto.fixProtoLibraryPatch
|
||||
import app.morphe.patches.shared.litho.addLithoFilter
|
||||
import app.morphe.patches.shared.litho.lithoFilterPatch
|
||||
import app.morphe.patches.youtube.shared.WatchNextResponseParserFingerprint
|
||||
import app.morphe.patches.youtube.utils.auth.authHookPatch
|
||||
import app.morphe.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.morphe.patches.youtube.utils.componentlist.hookElementList
|
||||
|
|
@ -17,6 +21,7 @@ import app.morphe.patches.youtube.utils.settings.settingsPatch
|
|||
import app.morphe.patches.youtube.video.information.videoInformationPatch
|
||||
import app.morphe.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
||||
import app.morphe.patches.youtube.video.videoid.videoIdPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"$COMPONENTS_PATH/ActionButtonsFilter;"
|
||||
|
|
@ -35,6 +40,7 @@ val actionButtonsPatch = bytecodePatch(
|
|||
lithoFilterPatch,
|
||||
lithoLayoutPatch,
|
||||
lazilyConvertedElementHookPatch,
|
||||
fixProtoLibraryPatch,
|
||||
videoInformationPatch,
|
||||
videoIdPatch,
|
||||
authHookPatch,
|
||||
|
|
@ -43,6 +49,7 @@ val actionButtonsPatch = bytecodePatch(
|
|||
|
||||
execute {
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
hookElementList("$FILTER_CLASS_DESCRIPTOR->onLazilyConvertedElementLoaded")
|
||||
|
||||
// region patch for hide action buttons by index
|
||||
|
||||
|
|
@ -51,6 +58,20 @@ val actionButtonsPatch = bytecodePatch(
|
|||
|
||||
// endregion
|
||||
|
||||
WatchNextResponseParserFingerprint.let {
|
||||
it.clearMatch()
|
||||
it.method.apply {
|
||||
val index = it.instructionMatches[5].index
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"invoke-static { v$register }, $FILTER_CLASS_DESCRIPTOR->" +
|
||||
"onSingleColumnWatchNextResultsLoaded(Lcom/google/protobuf/MessageLite;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2026 Morphe.
|
||||
* https://github.com/MorpheApp/morphe-patches
|
||||
*/
|
||||
|
||||
package app.morphe.patches.youtube.shared
|
||||
|
||||
import app.morphe.patcher.Fingerprint
|
||||
import app.morphe.patcher.InstructionLocation.MatchAfterImmediately
|
||||
import app.morphe.patcher.InstructionLocation.MatchAfterWithin
|
||||
import app.morphe.patcher.fieldAccess
|
||||
import app.morphe.patcher.literal
|
||||
import app.morphe.patcher.opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object WatchNextResponseParserFingerprint : Fingerprint(
|
||||
accessFlags = listOf(AccessFlags.PUBLIC, AccessFlags.FINAL),
|
||||
parameters = listOf("Ljava/lang/Object;"),
|
||||
returnType = "Ljava/util/List;",
|
||||
filters = listOf(
|
||||
literal(49399797L),
|
||||
opcode(Opcode.SGET_OBJECT),
|
||||
fieldAccess(
|
||||
opcode = Opcode.IGET_OBJECT,
|
||||
location = MatchAfterImmediately()
|
||||
),
|
||||
literal(51779735L),
|
||||
fieldAccess(
|
||||
opcode = Opcode.IGET_OBJECT,
|
||||
type = "Ljava/lang/Object;",
|
||||
location = MatchAfterWithin(5)
|
||||
),
|
||||
opcode(
|
||||
Opcode.CHECK_CAST,
|
||||
location = MatchAfterImmediately()
|
||||
),
|
||||
literal(46659098L),
|
||||
)
|
||||
)
|
||||
|
|
@ -659,14 +659,16 @@ Some components may not be hidden."</string>
|
|||
<string name="revanced_hide_action_button_index_summary_off">"Action buttons are hidden by identifier filter.
|
||||
|
||||
Info:
|
||||
• Right action buttons are hidden.
|
||||
• Hiding action buttons leaves empty space."</string>
|
||||
• Supported action buttons are removed without leaving empty space."</string>
|
||||
<string name="revanced_hide_action_button_index_summary_on">"Action buttons are hidden by index.
|
||||
|
||||
Info:
|
||||
• Wrong action buttons may be hidden, or action buttons may not be hidden.
|
||||
• Hiding action buttons leaves no empty space."</string>
|
||||
<string name="revanced_hide_action_button_index_title">Hide action button by index</string>
|
||||
<string name="revanced_hide_action_bar_summary_off">Action bar is shown.</string>
|
||||
<string name="revanced_hide_action_bar_summary_on">Action bar is hidden.</string>
|
||||
<string name="revanced_hide_action_bar_title">Hide action bar</string>
|
||||
<string name="revanced_hide_ai_chat_summary_summary_off">AI chat summary is shown.</string>
|
||||
<string name="revanced_hide_ai_chat_summary_summary_on">AI chat summary is hidden.</string>
|
||||
<string name="revanced_hide_ai_chat_summary_title">Hide AI chat summary</string>
|
||||
|
|
|
|||
|
|
@ -360,6 +360,7 @@
|
|||
|
||||
<!-- SETTINGS: HIDE_ACTION_BUTTONS
|
||||
<PreferenceScreen android:title="@string/revanced_preference_screen_action_buttons_title" android:key="revanced_preference_screen_action_buttons" android:summary="@string/revanced_preference_screen_action_buttons_summary">
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_bar_title" android:key="revanced_hide_action_bar" android:summaryOn="@string/revanced_hide_action_bar_summary_on" android:summaryOff="@string/revanced_hide_action_bar_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_like_dislike_button_title" android:key="revanced_hide_like_dislike_button" android:summaryOn="@string/revanced_hide_like_dislike_button_summary_on" android:summaryOff="@string/revanced_hide_like_dislike_button_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_disable_like_dislike_glow_title" android:key="revanced_disable_like_dislike_glow" android:summaryOn="@string/revanced_disable_like_dislike_glow_summary_on" android:summaryOff="@string/revanced_disable_like_dislike_glow_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_share_button_title" android:key="revanced_hide_share_button" android:summaryOn="@string/revanced_hide_share_button_summary_on" android:summaryOff="@string/revanced_hide_share_button_summary_off" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue