mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 21:27:43 +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:
@ -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);
|
||||
|
Reference in New Issue
Block a user