mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-29 22:24:31 +02:00
feat(YouTube - Hide action buttons): Add setting Hide action button by index
, Remove patch option Hide action buttons by index
This commit is contained in:
parent
4358a4739b
commit
89920480c7
@ -2,6 +2,7 @@ package app.revanced.extension.shared.patches.client
|
||||
|
||||
import android.os.Build
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
import app.revanced.extension.shared.utils.PackageUtils
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import java.util.Locale
|
||||
|
||||
@ -73,6 +74,15 @@ object YouTubeAppClient {
|
||||
iOSUserAgent(PACKAGE_NAME_IOS_UNPLUGGED, CLIENT_VERSION_IOS_UNPLUGGED)
|
||||
|
||||
|
||||
// ANDROID
|
||||
private const val PACKAGE_NAME_ANDROID = "com.google.android.youtube"
|
||||
private val CLIENT_VERSION_ANDROID = PackageUtils.getAppVersionName()
|
||||
private val USER_AGENT_ANDROID = androidUserAgent(
|
||||
packageName = PACKAGE_NAME_ANDROID,
|
||||
clientVersion = CLIENT_VERSION_ANDROID,
|
||||
)
|
||||
|
||||
|
||||
// ANDROID VR
|
||||
/**
|
||||
* Video not playable: Kids
|
||||
@ -91,7 +101,7 @@ object YouTubeAppClient {
|
||||
* [the App Store page of the YouTube app](https://www.meta.com/en-us/experiences/2002317119880945/),
|
||||
* in the `Additional details` section.
|
||||
*/
|
||||
private const val CLIENT_VERSION_ANDROID_VR = "1.61.48"
|
||||
private const val CLIENT_VERSION_ANDROID_VR = "1.62.27"
|
||||
|
||||
/**
|
||||
* The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client.
|
||||
@ -281,6 +291,14 @@ object YouTubeAppClient {
|
||||
*/
|
||||
val friendlyName: String
|
||||
) {
|
||||
ANDROID(
|
||||
id = 3,
|
||||
userAgent = USER_AGENT_ANDROID,
|
||||
androidSdkVersion = Build.VERSION.SDK,
|
||||
clientVersion = CLIENT_VERSION_ANDROID,
|
||||
clientName = "ANDROID",
|
||||
friendlyName = "Android"
|
||||
),
|
||||
ANDROID_VR(
|
||||
id = 28,
|
||||
deviceMake = DEVICE_MAKE_ANDROID_VR,
|
||||
|
@ -62,43 +62,20 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters causing playback issues.
|
||||
*/
|
||||
private static final String[] PATH_NO_VIDEO_ID = {
|
||||
"ad_break", // This request fetches a list of times when ads can be displayed.
|
||||
"get_drm_license", // Waiting for a paid video to start.
|
||||
"heartbeat", // This request determines whether to pause playback when the user is AFK.
|
||||
"refresh", // Waiting for a livestream to start.
|
||||
};
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
Uri uri = Uri.parse(url);
|
||||
String path = uri.getPath();
|
||||
if (path == null || !path.contains("player")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.containsAny(path, PATH_NO_VIDEO_ID)) {
|
||||
Logger.printDebug(() -> "Ignoring path: " + path);
|
||||
return;
|
||||
}
|
||||
|
||||
String id = uri.getQueryParameter("id");
|
||||
if (id == null) {
|
||||
Logger.printException(() -> "Ignoring request with no id: " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "fetchStreams failure", ex);
|
||||
String id = Utils.getVideoIdFromRequest(url);
|
||||
if (id == null) {
|
||||
Logger.printException(() -> "Ignoring request with no id: " + url);
|
||||
return;
|
||||
} else if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,17 @@ object PlayerRoutes {
|
||||
"&alt=proto"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_ACTION_BUTTON: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"next" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=contents.singleColumnWatchNextResults." +
|
||||
"results.results.contents.slimVideoMetadataSectionRenderer." +
|
||||
"contents.elementRenderer.newElement.type.componentType." +
|
||||
"model.videoActionBarModel.buttons.buttonViewModel"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_DETAILS: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
|
@ -110,20 +110,13 @@ class StreamingDataRequest private constructor(
|
||||
private const val HTTP_TIMEOUT_MILLISECONDS = 10 * 1000
|
||||
|
||||
/**
|
||||
* Any arbitrarily large value, but must be at least twice [.HTTP_TIMEOUT_MILLISECONDS]
|
||||
* Any arbitrarily large value, but must be at least twice [HTTP_TIMEOUT_MILLISECONDS]
|
||||
*/
|
||||
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||
|
||||
@GuardedBy("itself")
|
||||
val cache: MutableMap<String, StreamingDataRequest> = Collections.synchronizedMap(
|
||||
object : LinkedHashMap<String, StreamingDataRequest>(100) {
|
||||
/**
|
||||
* Cache limit must be greater than the maximum number of videos open at once,
|
||||
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
|
||||
* But instead use a much larger value, to handle if a video viewed a while ago
|
||||
* is somehow still referenced. Each stream is a small array of Strings
|
||||
* so memory usage is not a concern.
|
||||
*/
|
||||
private val CACHE_LIMIT = 50
|
||||
|
||||
override fun removeEldestEntry(eldest: Map.Entry<String, StreamingDataRequest>): Boolean {
|
||||
|
@ -11,6 +11,7 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@ -432,6 +433,34 @@ public class Utils {
|
||||
setEditTextDialogTheme(builder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* No video id in these parameters.
|
||||
*/
|
||||
private static final String[] PATH_NO_VIDEO_ID = {
|
||||
"ad_break", // This request fetches a list of times when ads can be displayed.
|
||||
"get_drm_license", // Waiting for a paid video to start.
|
||||
"heartbeat", // This request determines whether to pause playback when the user is AFK.
|
||||
"refresh", // Waiting for a livestream to start.
|
||||
};
|
||||
|
||||
@Nullable
|
||||
public static String getVideoIdFromRequest(String url) {
|
||||
try {
|
||||
Uri uri = Uri.parse(url);
|
||||
String path = uri.getPath();
|
||||
if (path != null && path.contains("player")) {
|
||||
if (!containsAny(path, PATH_NO_VIDEO_ID)) {
|
||||
return uri.getQueryParameter("id");
|
||||
} else {
|
||||
Logger.printDebug(() -> "Ignoring path: " + path);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "getVideoIdFromRequest failure", ex);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link Fragment} uses [Android library] rather than [AndroidX library],
|
||||
* the Dialog theme corresponding to [Android library] should be used.
|
||||
|
@ -6,6 +6,7 @@ import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
|
||||
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
|
||||
import app.revanced.extension.shared.patches.components.Filter;
|
||||
import app.revanced.extension.shared.patches.components.StringFilterGroup;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -18,6 +19,8 @@ public final class ActionButtonsFilter extends Filter {
|
||||
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,
|
||||
@ -95,6 +98,9 @@ public final class ActionButtonsFilter extends Filter {
|
||||
@Override
|
||||
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (HIDE_ACTION_BUTTON_INDEX) {
|
||||
return false;
|
||||
}
|
||||
if (!path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2,53 +2,144 @@ package app.revanced.extension.youtube.patches.player;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static app.revanced.extension.youtube.patches.player.ActionButtonsPatch.ActionButton.*;
|
||||
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
import app.revanced.extension.youtube.patches.player.requests.ActionButtonRequest;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.VideoInformation;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ActionButtonsPatch {
|
||||
|
||||
public enum ActionButton {
|
||||
INDEX_7(Settings.HIDE_ACTION_BUTTON_INDEX_7, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_7, 7),
|
||||
INDEX_6(Settings.HIDE_ACTION_BUTTON_INDEX_6, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_6, 6),
|
||||
INDEX_5(Settings.HIDE_ACTION_BUTTON_INDEX_5, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_5, 5),
|
||||
INDEX_4(Settings.HIDE_ACTION_BUTTON_INDEX_4, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_4, 4),
|
||||
INDEX_3(Settings.HIDE_ACTION_BUTTON_INDEX_3, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_3, 3),
|
||||
INDEX_2(Settings.HIDE_ACTION_BUTTON_INDEX_2, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_2, 2),
|
||||
INDEX_1(Settings.HIDE_ACTION_BUTTON_INDEX_1, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_1, 1),
|
||||
INDEX_0(Settings.HIDE_ACTION_BUTTON_INDEX_0, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_0, 0);
|
||||
UNKNOWN(
|
||||
null,
|
||||
null
|
||||
),
|
||||
CLIP(
|
||||
"clipButtonViewModel",
|
||||
Settings.HIDE_CLIP_BUTTON
|
||||
),
|
||||
DOWNLOAD(
|
||||
"downloadButtonViewModel",
|
||||
Settings.HIDE_DOWNLOAD_BUTTON
|
||||
),
|
||||
LIKE_DISLIKE(
|
||||
"segmentedLikeDislikeButtonViewModel",
|
||||
Settings.HIDE_LIKE_DISLIKE_BUTTON
|
||||
),
|
||||
LIVE_CHAT(
|
||||
"yt_outline_message_bubble",
|
||||
null
|
||||
),
|
||||
PLAYLIST(
|
||||
"addToPlaylistButtonViewModel",
|
||||
Settings.HIDE_PLAYLIST_BUTTON
|
||||
),
|
||||
REMIX(
|
||||
"yt_outline_youtube_shorts_plus",
|
||||
Settings.HIDE_REMIX_BUTTON
|
||||
),
|
||||
REPORT(
|
||||
"yt_outline_flag",
|
||||
Settings.HIDE_REPORT_BUTTON
|
||||
),
|
||||
REWARDS(
|
||||
"yt_outline_account_link",
|
||||
Settings.HIDE_REWARDS_BUTTON
|
||||
),
|
||||
SHARE(
|
||||
"yt_outline_share",
|
||||
Settings.HIDE_SHARE_BUTTON
|
||||
),
|
||||
SHOP(
|
||||
"yt_outline_bag",
|
||||
Settings.HIDE_SHOP_BUTTON
|
||||
),
|
||||
THANKS(
|
||||
"yt_outline_dollar_sign_heart",
|
||||
Settings.HIDE_THANKS_BUTTON
|
||||
);
|
||||
|
||||
private final BooleanSetting generalSetting;
|
||||
private final BooleanSetting liveSetting;
|
||||
private final int index;
|
||||
@Nullable
|
||||
public final String identifier;
|
||||
@Nullable
|
||||
public final BooleanSetting setting;
|
||||
|
||||
ActionButton(final BooleanSetting generalSetting, final BooleanSetting liveSetting, final int index) {
|
||||
this.generalSetting = generalSetting;
|
||||
this.liveSetting = liveSetting;
|
||||
this.index = index;
|
||||
ActionButton(@Nullable String identifier, @Nullable BooleanSetting setting) {
|
||||
this.identifier = identifier;
|
||||
this.setting = setting;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TARGET_COMPONENT_TYPE = "LazilyConvertedElement";
|
||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
||||
private static final boolean HIDE_ACTION_BUTTON_INDEX = Settings.HIDE_ACTION_BUTTON_INDEX.get();
|
||||
private static final int REMIX_INDEX = Settings.REMIX_BUTTON_INDEX.get() - 1;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
||||
if (HIDE_ACTION_BUTTON_INDEX) {
|
||||
String id = Utils.getVideoIdFromRequest(url);
|
||||
if (id == null) {
|
||||
Logger.printException(() -> "Ignoring request with no id: " + url);
|
||||
return;
|
||||
} else if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActionButtonRequest.fetchRequestIfNeeded(id, requestHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* @param list Type list of litho components
|
||||
* @param identifier Identifier of litho components
|
||||
*/
|
||||
public static List<Object> hideActionButtonByIndex(@Nullable List<Object> list, @Nullable String identifier) {
|
||||
try {
|
||||
if (identifier != null &&
|
||||
if (HIDE_ACTION_BUTTON_INDEX &&
|
||||
identifier != null &&
|
||||
identifier.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) &&
|
||||
list != null &&
|
||||
!list.isEmpty() &&
|
||||
list.get(0).toString().equals(TARGET_COMPONENT_TYPE)
|
||||
) {
|
||||
final int size = list.size();
|
||||
final boolean isLive = VideoInformation.getLiveStreamState();
|
||||
for (ActionButton button : ActionButton.values()) {
|
||||
if (size > button.index && (isLive ? button.liveSetting.get() : button.generalSetting.get())) {
|
||||
list.remove(button.index);
|
||||
final int listSize = list.size();
|
||||
final String videoId = VideoInformation.getVideoId();
|
||||
ActionButtonRequest request = ActionButtonRequest.getRequestForVideoId(videoId);
|
||||
if (request != null) {
|
||||
ActionButton[] actionButtons = request.getArray();
|
||||
final int actionButtonsLength = actionButtons.length;
|
||||
// The response is always included with the [LIKE_DISLIKE] button and the [SHARE] button.
|
||||
// The minimum size of the action button array is 3.
|
||||
if (actionButtonsLength > 2) {
|
||||
// For some reason, the response does not contain the [REMIX] button.
|
||||
// Add the [REMIX] button manually.
|
||||
if (listSize - actionButtonsLength == 1) {
|
||||
actionButtons = ArrayUtils.add(actionButtons, REMIX_INDEX, REMIX);
|
||||
}
|
||||
ActionButton[] finalActionButtons = actionButtons;
|
||||
Logger.printDebug(() -> "videoId: " + videoId + ", buttons: " + Arrays.toString(finalActionButtons));
|
||||
for (int i = actionButtons.length - 1; i > -1; i--) {
|
||||
ActionButton actionButton = actionButtons[i];
|
||||
if (actionButton.setting != null && actionButton.setting.get()) {
|
||||
list.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,226 @@
|
||||
package app.revanced.extension.youtube.patches.player.requests
|
||||
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import app.revanced.extension.youtube.patches.player.ActionButtonsPatch.ActionButton
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.Collections
|
||||
import java.util.Objects
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class ActionButtonRequest private constructor(
|
||||
private val videoId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Array<ActionButton>> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId, playerHeaders)
|
||||
}
|
||||
|
||||
val array: Array<ActionButton>
|
||||
get() {
|
||||
try {
|
||||
return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS]
|
||||
} catch (ex: TimeoutException) {
|
||||
Logger.printInfo(
|
||||
{ "getArray timed out" },
|
||||
ex
|
||||
)
|
||||
} catch (ex: InterruptedException) {
|
||||
Logger.printException(
|
||||
{ "getArray interrupted" },
|
||||
ex
|
||||
)
|
||||
Thread.currentThread().interrupt() // Restore interrupt status flag.
|
||||
} catch (ex: ExecutionException) {
|
||||
Logger.printException(
|
||||
{ "getArray failure" },
|
||||
ex
|
||||
)
|
||||
}
|
||||
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* TCP connection and HTTP read timeout.
|
||||
*/
|
||||
private const val HTTP_TIMEOUT_MILLISECONDS = 10 * 1000
|
||||
|
||||
/**
|
||||
* Any arbitrarily large value, but must be at least twice [HTTP_TIMEOUT_MILLISECONDS]
|
||||
*/
|
||||
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||
|
||||
@GuardedBy("itself")
|
||||
val cache: MutableMap<String, ActionButtonRequest> = Collections.synchronizedMap(
|
||||
object : LinkedHashMap<String, ActionButtonRequest>(100) {
|
||||
private val CACHE_LIMIT = 50
|
||||
|
||||
override fun removeEldestEntry(eldest: Map.Entry<String, ActionButtonRequest>): Boolean {
|
||||
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
|
||||
}
|
||||
})
|
||||
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(videoId)) {
|
||||
cache[videoId] = ActionButtonRequest(videoId, playerHeaders)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRequestForVideoId(videoId: String): ActionButtonRequest? {
|
||||
synchronized(cache) {
|
||||
return cache[videoId]
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConnectionError(toastMessage: String, ex: Exception?) {
|
||||
Logger.printInfo({ toastMessage }, ex)
|
||||
}
|
||||
|
||||
private val REQUEST_HEADER_KEYS = arrayOf(
|
||||
"Authorization", // Available only to logged-in users.
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
"X-Goog-Visitor-Id"
|
||||
)
|
||||
|
||||
private fun sendRequest(videoId: String, playerHeaders: Map<String, String>): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// '/next' request does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_VIDEO_ACTION_BUTTON,
|
||||
clientType
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
// Since [THANKS] button and [CLIP] button are shown only with the logged in,
|
||||
// Set the [Authorization] field to property to get the correct action buttons.
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId
|
||||
)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == 200) return Requester.parseJSONObject(connection)
|
||||
|
||||
handleConnectionError(
|
||||
(clientTypeName + " not available with response code: "
|
||||
+ responseCode + " message: " + connection.responseMessage),
|
||||
null
|
||||
)
|
||||
} catch (ex: SocketTimeoutException) {
|
||||
handleConnectionError("Connection timeout", ex)
|
||||
} catch (ex: IOException) {
|
||||
handleConnectionError("Network error", ex)
|
||||
} catch (ex: Exception) {
|
||||
Logger.printException({ "sendApplicationRequest failed" }, ex)
|
||||
} finally {
|
||||
Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseResponse(json: JSONObject): Array<ActionButton> {
|
||||
try {
|
||||
val secondaryContentsJsonObject =
|
||||
json.getJSONObject("contents")
|
||||
.getJSONObject("singleColumnWatchNextResults")
|
||||
.getJSONObject("results")
|
||||
.getJSONObject("results")
|
||||
.getJSONArray("contents")
|
||||
.get(0)
|
||||
|
||||
if (secondaryContentsJsonObject is JSONObject) {
|
||||
val tertiaryContentsJsonArray =
|
||||
secondaryContentsJsonObject
|
||||
.getJSONObject("slimVideoMetadataSectionRenderer")
|
||||
.getJSONArray("contents")
|
||||
|
||||
val elementRendererJsonObject =
|
||||
tertiaryContentsJsonArray
|
||||
.get(tertiaryContentsJsonArray.length() - 1)
|
||||
|
||||
if (elementRendererJsonObject is JSONObject) {
|
||||
val buttons =
|
||||
elementRendererJsonObject
|
||||
.getJSONObject("elementRenderer")
|
||||
.getJSONObject("newElement")
|
||||
.getJSONObject("type")
|
||||
.getJSONObject("componentType")
|
||||
.getJSONObject("model")
|
||||
.getJSONObject("videoActionBarModel")
|
||||
.getJSONArray("buttons")
|
||||
|
||||
val length = buttons.length()
|
||||
val buttonsArr = Array<ActionButton>(length) { ActionButton.UNKNOWN }
|
||||
|
||||
for (i in 0 until length) {
|
||||
val jsonObjectString = buttons.get(i).toString()
|
||||
for (b in ActionButton.entries) {
|
||||
if (b.identifier != null && jsonObjectString.contains(b.identifier)) {
|
||||
buttonsArr[i] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still, the response includes the [LIVE_CHAT] button.
|
||||
// In the Android YouTube client, this button moved to the comments.
|
||||
return buttonsArr.filter { it.setting != null }.toTypedArray()
|
||||
}
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
val jsonForMessage = json.toString().substring(3000)
|
||||
Logger.printException(
|
||||
{ "Fetch failed while processing response data for response: $jsonForMessage" },
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Array<ActionButton> {
|
||||
val json = sendRequest(videoId, playerHeaders)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
||||
return emptyArray()
|
||||
}
|
||||
}
|
||||
}
|
@ -291,22 +291,8 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", FALSE);
|
||||
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_0 = new BooleanSetting("revanced_hide_action_button_index_0", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_1 = new BooleanSetting("revanced_hide_action_button_index_1", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_2 = new BooleanSetting("revanced_hide_action_button_index_2", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_3 = new BooleanSetting("revanced_hide_action_button_index_3", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_4 = new BooleanSetting("revanced_hide_action_button_index_4", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_5 = new BooleanSetting("revanced_hide_action_button_index_5", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_6 = new BooleanSetting("revanced_hide_action_button_index_6", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_7 = new BooleanSetting("revanced_hide_action_button_index_7", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_0 = new BooleanSetting("revanced_hide_action_button_index_live_0", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_1 = new BooleanSetting("revanced_hide_action_button_index_live_1", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_2 = new BooleanSetting("revanced_hide_action_button_index_live_2", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_3 = new BooleanSetting("revanced_hide_action_button_index_live_3", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_4 = new BooleanSetting("revanced_hide_action_button_index_live_4", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_5 = new BooleanSetting("revanced_hide_action_button_index_live_5", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_6 = new BooleanSetting("revanced_hide_action_button_index_live_6", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_7 = new BooleanSetting("revanced_hide_action_button_index_live_7", FALSE);
|
||||
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX = new BooleanSetting("revanced_hide_action_button_index", FALSE, true);
|
||||
public static final IntegerSetting REMIX_BUTTON_INDEX = new IntegerSetting("revanced_remix_button_index", 3, true, parent(HIDE_ACTION_BUTTON_INDEX));
|
||||
|
||||
// PreferenceScreen: Player - Ambient mode
|
||||
public static final BooleanSetting BYPASS_AMBIENT_MODE_RESTRICTIONS = new BooleanSetting("revanced_bypass_ambient_mode_restrictions", FALSE);
|
||||
|
@ -1,7 +1,6 @@
|
||||
package app.revanced.patches.youtube.player.action
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.booleanOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.litho.addLithoFilter
|
||||
import app.revanced.patches.shared.litho.emptyComponentsFingerprint
|
||||
@ -10,10 +9,11 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH
|
||||
import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_ACTION_BUTTONS
|
||||
import app.revanced.patches.youtube.utils.request.buildRequestPatch
|
||||
import app.revanced.patches.youtube.utils.request.hookBuildRequest
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.information.videoInformationPatch
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
@ -44,72 +44,61 @@ val actionButtonsPatch = bytecodePatch(
|
||||
settingsPatch,
|
||||
lithoFilterPatch,
|
||||
videoInformationPatch,
|
||||
)
|
||||
|
||||
val hideActionButtonByIndex by booleanOption(
|
||||
key = "hideActionButtonByIndex",
|
||||
default = false,
|
||||
title = "Hide action buttons by index",
|
||||
description = """
|
||||
Add an option to hide action buttons by index.
|
||||
|
||||
This setting is still experimental, so use it only for debugging purposes.
|
||||
""".trimIndentMultiline(),
|
||||
required = true
|
||||
buildRequestPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
|
||||
var settingArray = arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: HIDE_ACTION_BUTTONS"
|
||||
)
|
||||
// region patch for hide action buttons by index
|
||||
|
||||
if (hideActionButtonByIndex == true) {
|
||||
componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply {
|
||||
val conversionContextToStringMethod =
|
||||
findMethodOrThrow(parameters[1].type) {
|
||||
name == "toString"
|
||||
}
|
||||
val identifierReference = with (conversionContextToStringMethod) {
|
||||
val identifierStringIndex =
|
||||
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
|
||||
val identifierStringAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
|
||||
val identifierAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierRegister = getInstruction<FiveRegisterInstruction>(identifierAppendIndex).registerD
|
||||
val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;" &&
|
||||
(this as? TwoRegisterInstruction)?.registerA == identifierRegister
|
||||
}
|
||||
getInstruction<ReferenceInstruction>(identifierIndex).reference
|
||||
componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply {
|
||||
val conversionContextToStringMethod =
|
||||
findMethodOrThrow(parameters[1].type) {
|
||||
name == "toString"
|
||||
}
|
||||
|
||||
val listIndex = implementation!!.instructions.lastIndex
|
||||
val listRegister = getInstruction<OneRegisterInstruction>(listIndex).registerA
|
||||
val identifierRegister = listRegister + 1
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
listIndex, """
|
||||
move-object/from16 v$identifierRegister, p2
|
||||
iget-object v$identifierRegister, v$identifierRegister, $identifierReference
|
||||
invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
|
||||
move-result-object v$listRegister
|
||||
"""
|
||||
)
|
||||
|
||||
settingArray += "SETTINGS: HIDE_BUTTONS_BY_INDEX"
|
||||
val identifierReference = with (conversionContextToStringMethod) {
|
||||
val identifierStringIndex =
|
||||
indexOfFirstStringInstructionOrThrow(", identifierProperty=")
|
||||
val identifierStringAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierStringAppendIndexRegister = getInstruction<FiveRegisterInstruction>(identifierStringAppendIndex).registerD
|
||||
val identifierAppendIndex =
|
||||
indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL)
|
||||
val identifierRegister = getInstruction<FiveRegisterInstruction>(identifierAppendIndex).registerD
|
||||
val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) {
|
||||
opcode == Opcode.IGET_OBJECT &&
|
||||
getReference<FieldReference>()?.type == "Ljava/lang/String;" &&
|
||||
(this as? TwoRegisterInstruction)?.registerA == identifierRegister
|
||||
}
|
||||
getInstruction<ReferenceInstruction>(identifierIndex).reference
|
||||
}
|
||||
|
||||
val listIndex = implementation!!.instructions.lastIndex
|
||||
val listRegister = getInstruction<OneRegisterInstruction>(listIndex).registerA
|
||||
val identifierRegister = listRegister + 1
|
||||
|
||||
addInstructionsAtControlFlowLabel(
|
||||
listIndex, """
|
||||
move-object/from16 v$identifierRegister, p2
|
||||
iget-object v$identifierRegister, v$identifierRegister, $identifierReference
|
||||
invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List;
|
||||
move-result-object v$listRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
hookBuildRequest("$ACTION_BUTTONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V")
|
||||
|
||||
// endregion
|
||||
|
||||
// region add settings
|
||||
|
||||
addPreference(
|
||||
settingArray,
|
||||
arrayOf(
|
||||
"PREFERENCE_SCREEN: PLAYER",
|
||||
"SETTINGS: HIDE_ACTION_BUTTONS"
|
||||
),
|
||||
HIDE_ACTION_BUTTONS
|
||||
)
|
||||
|
||||
|
@ -33,38 +33,6 @@ internal val buildMediaDataSourceFingerprint = legacyFingerprint(
|
||||
)
|
||||
)
|
||||
|
||||
internal val buildRequestFingerprint = legacyFingerprint(
|
||||
name = "buildRequestFingerprint",
|
||||
customFingerprint = { method, _ ->
|
||||
method.implementation != null &&
|
||||
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
|
||||
!method.definingClass.startsWith("Lorg/") &&
|
||||
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
|
||||
// Earlier targets
|
||||
(indexOfEntrySetInstruction(method) >= 0 ||
|
||||
// Later targets
|
||||
method.parameters[1].type == "Ljava/util/Map;")
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setRequestFinishedListener"
|
||||
}
|
||||
|
||||
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;"
|
||||
}
|
||||
|
||||
internal fun indexOfEntrySetInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
|
||||
}
|
||||
|
||||
internal val createStreamingDataFingerprint = legacyFingerprint(
|
||||
name = "createStreamingDataFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
|
@ -18,6 +18,8 @@ import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
|
||||
import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA
|
||||
import app.revanced.patches.youtube.utils.request.buildRequestPatch
|
||||
import app.revanced.patches.youtube.utils.request.hookBuildRequest
|
||||
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
@ -31,7 +33,6 @@ import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
@ -39,7 +40,7 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$SPOOF_PATH/SpoofStreamingDataPatch;"
|
||||
|
||||
val spoofStreamingDataPatch = bytecodePatch(
|
||||
@ -52,36 +53,14 @@ val spoofStreamingDataPatch = bytecodePatch(
|
||||
settingsPatch,
|
||||
baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
|
||||
blockRequestPatch,
|
||||
buildRequestPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
buildRequestFingerprint.methodOrThrow().apply {
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
val urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val mapRegister = if (entrySetIndex < 0)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
var smaliInstructions =
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
|
||||
|
||||
if (entrySetIndex < 0) smaliInstructions = """
|
||||
move-object/from16 v$mapRegister, p1
|
||||
|
||||
""" + smaliInstructions
|
||||
|
||||
// Copy request headers for streaming data fetch.
|
||||
addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
|
||||
}
|
||||
hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V")
|
||||
|
||||
// endregion
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
package app.revanced.patches.youtube.utils.request
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
|
||||
private lateinit var buildRequestMethod: MutableMethod
|
||||
private var urlRegister = 0
|
||||
private var mapRegister = 0
|
||||
private var offSet = 0
|
||||
|
||||
val buildRequestPatch = bytecodePatch(
|
||||
description = "buildRequestPatch",
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
buildRequestFingerprint.methodOrThrow().apply {
|
||||
buildRequestMethod = this
|
||||
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
urlRegister =
|
||||
getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
|
||||
val entrySetIndex = indexOfEntrySetInstruction(this)
|
||||
val isLegacyTarget = entrySetIndex < 0
|
||||
mapRegister = if (isLegacyTarget)
|
||||
urlRegister + 1
|
||||
else
|
||||
getInstruction<FiveRegisterInstruction>(entrySetIndex).registerC
|
||||
|
||||
if (isLegacyTarget) {
|
||||
addInstructions(
|
||||
newRequestBuilderIndex + 2,
|
||||
"move-object/from16 v$mapRegister, p1"
|
||||
)
|
||||
offSet++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun hookBuildRequest(descriptor: String) {
|
||||
buildRequestMethod.apply {
|
||||
val insertIndex = indexOfNewUrlRequestBuilderInstruction(this) + 2 + offSet
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"invoke-static { v$urlRegister, v$mapRegister }, $descriptor"
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package app.revanced.patches.youtube.utils.request
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val buildRequestFingerprint = legacyFingerprint(
|
||||
name = "buildRequestFingerprint",
|
||||
customFingerprint = { method, _ ->
|
||||
method.implementation != null &&
|
||||
indexOfRequestFinishedListenerInstruction(method) >= 0 &&
|
||||
!method.definingClass.startsWith("Lorg/") &&
|
||||
indexOfNewUrlRequestBuilderInstruction(method) >= 0 &&
|
||||
// Earlier targets
|
||||
(indexOfEntrySetInstruction(method) >= 0 ||
|
||||
// Later targets
|
||||
method.parameters[1].type == "Ljava/util/Map;")
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfRequestFinishedListenerInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "setRequestFinishedListener"
|
||||
}
|
||||
|
||||
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;"
|
||||
}
|
||||
|
||||
internal fun indexOfEntrySetInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_INTERFACE &&
|
||||
getReference<MethodReference>().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;"
|
||||
}
|
@ -435,6 +435,22 @@
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_remix_button_index_entries">
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_remix_button_index_entry_values">
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_watch_history_type_entries">
|
||||
<item>@string/revanced_watch_history_type_entry_1</item>
|
||||
<item>@string/revanced_watch_history_type_entry_2</item>
|
||||
|
@ -891,42 +891,19 @@ Settings → Autoplay / Playback → Autoplay next video"</string>
|
||||
<string name="revanced_hide_thanks_button_summary_on">Thanks button is hidden.</string>
|
||||
<string name="revanced_hide_thanks_button_summary_off">Thanks button is shown.</string>
|
||||
|
||||
<!-- PreferenceScreen: Player, PreferenceCategory: Player, PreferenceScreen: Action buttons, PreferenceCategory: Hide by index -->
|
||||
<string name="revanced_preference_category_hide_by_index">Hide by index</string>
|
||||
<!-- PreferenceScreen: Player, PreferenceCategory: Player, PreferenceScreen: Action buttons, PreferenceCategory: Experimental flags -->
|
||||
<string name="revanced_hide_action_button_index_title">Hide action button by index</string>
|
||||
<string name="revanced_hide_action_button_index_summary_on">"Action buttons are hidden by index.
|
||||
|
||||
<string name="revanced_hide_action_button_index_0_title">Hide first button</string>
|
||||
<string name="revanced_hide_action_button_index_0_summary_on">First button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_0_summary_off">First button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_1_title">Hide second button</string>
|
||||
<string name="revanced_hide_action_button_index_1_summary_on">Second button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_1_summary_off">Second button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_2_title">Hide third button</string>
|
||||
<string name="revanced_hide_action_button_index_2_summary_on">Third button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_2_summary_off">Third button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_3_title">Hide fourth button</string>
|
||||
<string name="revanced_hide_action_button_index_3_summary_on">Fourth button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_3_summary_off">Fourth button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_4_title">Hide fifth button</string>
|
||||
<string name="revanced_hide_action_button_index_4_summary_on">Fifth button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_4_summary_off">Fifth button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_5_title">Hide sixth button</string>
|
||||
<string name="revanced_hide_action_button_index_5_summary_on">Sixth button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_5_summary_off">Sixth button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_6_title">Hide seventh button</string>
|
||||
<string name="revanced_hide_action_button_index_6_summary_on">Seventh button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_6_summary_off">Seventh button is shown.</string>
|
||||
<string name="revanced_hide_action_button_index_7_title">Hide eighth button</string>
|
||||
<string name="revanced_hide_action_button_index_7_summary_on">Eighth button is hidden.</string>
|
||||
<string name="revanced_hide_action_button_index_7_summary_off">Eighth button is shown.</string>
|
||||
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_summary_off">"Action buttons are hidden by identifier filter.
|
||||
|
||||
<!-- PreferenceScreen: Player, PreferenceCategory: Player, PreferenceScreen: Action buttons, PreferenceCategory: Hide by index in live stream -->
|
||||
<string name="revanced_preference_category_hide_by_index_live">Hide by index in live stream</string>
|
||||
|
||||
<string name="revanced_hide_action_button_index_about_title">About Hide action button by index</string>
|
||||
<string name="revanced_hide_action_button_index_about_summary">"Hide the action buttons by index before the action buttons are initialized.
|
||||
|
||||
- Hiding the action buttons leaves no empty space.
|
||||
- Index of the action buttons may not always be the same button."</string>
|
||||
Info:
|
||||
• Right action buttons are hidden.
|
||||
• Hiding action buttons leaves empty space."</string>
|
||||
<string name="revanced_remix_button_index_title">Remix button index</string>
|
||||
|
||||
<!-- PreferenceScreen: Player, PreferenceCategory: Player, PreferenceScreen: Ambient mode -->
|
||||
<string name="revanced_preference_screen_ambient_mode_title">Ambient mode</string>
|
||||
|
@ -345,30 +345,10 @@
|
||||
<SwitchPreference android:title="@string/revanced_hide_playlist_button_title" android:key="revanced_hide_playlist_button" android:summaryOn="@string/revanced_hide_playlist_button_summary_on" android:summaryOff="@string/revanced_hide_playlist_button_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" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_shop_button_title" android:key="revanced_hide_shop_button" android:summaryOn="@string/revanced_hide_shop_button_summary_on" android:summaryOff="@string/revanced_hide_shop_button_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_thanks_button_title" android:key="revanced_hide_thanks_button" android:summaryOn="@string/revanced_hide_thanks_button_summary_on" android:summaryOff="@string/revanced_hide_thanks_button_summary_off" />SETTINGS: HIDE_ACTION_BUTTONS -->
|
||||
|
||||
<!-- SETTINGS: HIDE_BUTTONS_BY_INDEX
|
||||
<PreferenceCategory android:title="@string/revanced_preference_category_hide_by_index" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_0_title" android:key="revanced_hide_action_button_index_0" android:summaryOn="@string/revanced_hide_action_button_index_0_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_0_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_1_title" android:key="revanced_hide_action_button_index_1" android:summaryOn="@string/revanced_hide_action_button_index_1_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_1_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_2_title" android:key="revanced_hide_action_button_index_2" android:summaryOn="@string/revanced_hide_action_button_index_2_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_2_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_3_title" android:key="revanced_hide_action_button_index_3" android:summaryOn="@string/revanced_hide_action_button_index_3_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_3_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_4_title" android:key="revanced_hide_action_button_index_4" android:summaryOn="@string/revanced_hide_action_button_index_4_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_4_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_5_title" android:key="revanced_hide_action_button_index_5" android:summaryOn="@string/revanced_hide_action_button_index_5_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_5_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_6_title" android:key="revanced_hide_action_button_index_6" android:summaryOn="@string/revanced_hide_action_button_index_6_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_6_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_7_title" android:key="revanced_hide_action_button_index_7" android:summaryOn="@string/revanced_hide_action_button_index_7_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_7_summary_off" />
|
||||
|
||||
<PreferenceCategory android:title="@string/revanced_preference_category_hide_by_index_live" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_0_title" android:key="revanced_hide_action_button_index_live_0" android:summaryOn="@string/revanced_hide_action_button_index_0_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_0_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_1_title" android:key="revanced_hide_action_button_index_live_1" android:summaryOn="@string/revanced_hide_action_button_index_1_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_1_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_2_title" android:key="revanced_hide_action_button_index_live_2" android:summaryOn="@string/revanced_hide_action_button_index_2_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_2_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_3_title" android:key="revanced_hide_action_button_index_live_3" android:summaryOn="@string/revanced_hide_action_button_index_3_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_3_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_4_title" android:key="revanced_hide_action_button_index_live_4" android:summaryOn="@string/revanced_hide_action_button_index_4_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_4_summary_off" />
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_5_title" android:key="revanced_hide_action_button_index_live_5" android:summaryOn="@string/revanced_hide_action_button_index_5_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_5_summary_off" />
|
||||
|
||||
<Preference android:title="@string/revanced_hide_action_button_index_about_title" android:selectable="false" android:summary="@string/revanced_hide_action_button_index_about_summary" />SETTINGS: HIDE_BUTTONS_BY_INDEX -->
|
||||
|
||||
<!-- SETTINGS: HIDE_ACTION_BUTTONS
|
||||
<SwitchPreference android:title="@string/revanced_hide_thanks_button_title" android:key="revanced_hide_thanks_button" android:summaryOn="@string/revanced_hide_thanks_button_summary_on" android:summaryOff="@string/revanced_hide_thanks_button_summary_off" />
|
||||
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||
<SwitchPreference android:title="@string/revanced_hide_action_button_index_title" android:key="revanced_hide_action_button_index" android:summaryOn="@string/revanced_hide_action_button_index_summary_on" android:summaryOff="@string/revanced_hide_action_button_index_summary_off" />
|
||||
<ListPreference android:entries="@array/revanced_remix_button_index_entries" android:title="@string/revanced_remix_button_index_title" android:key="revanced_remix_button_index" android:entryValues="@array/revanced_remix_button_index_entry_values" />
|
||||
</PreferenceScreen>SETTINGS: HIDE_ACTION_BUTTONS -->
|
||||
|
||||
<!-- SETTINGS: AMBIENT_MODE_CONTROLS
|
||||
|
Loading…
x
Reference in New Issue
Block a user