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:
Aaron Veil 2026-03-23 19:32:34 +03:00
parent 34e3d92456
commit bda1ddba69
7 changed files with 540 additions and 143 deletions

View file

@ -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);
}
}
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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(

View file

@ -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),
)
)

View file

@ -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>

View file

@ -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" />