mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-30 14:44:30 +02:00
refactor(InnterTube): Move classes to the appropriate path
This commit is contained in:
parent
4bed9f346d
commit
123082b676
@ -2,8 +2,10 @@ package app.revanced.extension.music.patches.misc.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLIST_PAGE
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.settings.AppLanguage
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
@ -136,10 +138,11 @@ class PlaylistRequest private constructor(
|
||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_PLAYLIST_PAGE,
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_PLAYLIST_PAGE,
|
||||
clientType
|
||||
)
|
||||
|
||||
/**
|
||||
* For some reason, the tracks in Top Songs have the playlistId of the album:
|
||||
* [ReVanced_Extended#2835](https://github.com/inotia00/ReVanced_Extended/issues/2835)
|
||||
@ -152,7 +155,7 @@ class PlaylistRequest private constructor(
|
||||
* So we can work around this by setting the language to English when sending the request.
|
||||
*/
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
playlistId = playlistId,
|
||||
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.extension.shared.patches.client
|
||||
package app.revanced.extension.shared.innertube.client
|
||||
|
||||
import android.os.Build
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
@ -11,24 +11,6 @@ import java.util.Locale
|
||||
*/
|
||||
object YouTubeAppClient {
|
||||
// IOS
|
||||
/**
|
||||
* Video not playable: Paid / Movie / Private / Age-restricted
|
||||
* Note: Audio track available
|
||||
*/
|
||||
private const val PACKAGE_NAME_IOS = "com.google.ios.youtube"
|
||||
|
||||
/**
|
||||
* The hardcoded client version of the iOS app used for InnerTube requests with this client.
|
||||
*
|
||||
* It can be extracted by getting the latest release version of the app on
|
||||
* [the App Store page of the YouTube app](https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664/),
|
||||
* in the `What’s New` section.
|
||||
*/
|
||||
private val CLIENT_VERSION_IOS = if (forceAVC())
|
||||
"17.40.5"
|
||||
else
|
||||
"20.10.4"
|
||||
|
||||
private const val DEVICE_MAKE_IOS = "Apple"
|
||||
private const val OS_NAME_IOS = "iOS"
|
||||
|
||||
@ -49,7 +31,6 @@ object YouTubeAppClient {
|
||||
"13_7"
|
||||
else
|
||||
"18_3_2"
|
||||
private val USER_AGENT_IOS = iOSUserAgent(PACKAGE_NAME_IOS, CLIENT_VERSION_IOS)
|
||||
|
||||
|
||||
// IOS UNPLUGGED
|
||||
@ -202,6 +183,7 @@ object YouTubeAppClient {
|
||||
* Example: 'com.google.ios.youtube/16.38.2 (iPhone9,4; U; CPU iOS 14_7_1 like Mac OS X; en_AU)'
|
||||
* Source: https://github.com/mitmproxy/mitmproxy/issues/4836.
|
||||
*/
|
||||
@Suppress("SameParameterValue")
|
||||
private fun iOSUserAgent(
|
||||
packageName: String,
|
||||
clientVersion: String
|
||||
@ -278,10 +260,6 @@ object YouTubeAppClient {
|
||||
* If true, 'Authorization' must be included.
|
||||
*/
|
||||
val requireAuth: Boolean = false,
|
||||
/**
|
||||
* Whether a poToken is required to get playback for more than 1 minute.
|
||||
*/
|
||||
val requirePoToken: Boolean = false,
|
||||
/**
|
||||
* Client name for innertube body.
|
||||
*/
|
||||
@ -362,22 +340,6 @@ object YouTubeAppClient {
|
||||
"iOS TV Force AVC"
|
||||
else
|
||||
"iOS TV"
|
||||
),
|
||||
IOS(
|
||||
id = 5,
|
||||
deviceMake = DEVICE_MAKE_IOS,
|
||||
deviceModel = DEVICE_MODEL_IOS,
|
||||
osName = OS_NAME_IOS,
|
||||
osVersion = OS_VERSION_IOS,
|
||||
userAgent = USER_AGENT_IOS,
|
||||
clientVersion = CLIENT_VERSION_IOS,
|
||||
supportsCookies = false,
|
||||
requirePoToken = true,
|
||||
clientName = "IOS",
|
||||
friendlyName = if (forceAVC())
|
||||
"iOS Force AVC"
|
||||
else
|
||||
"iOS"
|
||||
);
|
||||
|
||||
companion object {
|
@ -1,10 +1,10 @@
|
||||
package app.revanced.extension.shared.patches.client;
|
||||
package app.revanced.extension.shared.innertube.client;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class MusicAppClient {
|
||||
public class YouTubeMusicAppClient {
|
||||
|
||||
// Response to the '/next' request is 'Please update to continue using the app':
|
||||
// https://github.com/inotia00/ReVanced_Extended/issues/2743
|
||||
@ -46,7 +46,7 @@ public class MusicAppClient {
|
||||
private static final String DEVICE_MAKE_IOS_MUSIC = "Apple";
|
||||
private static final String OS_NAME_IOS_MUSIC = "iOS";
|
||||
|
||||
private MusicAppClient() {
|
||||
private YouTubeMusicAppClient() {
|
||||
}
|
||||
|
||||
private static String androidUserAgent(String clientVersion) {
|
@ -1,14 +1,10 @@
|
||||
package app.revanced.extension.shared.patches.client
|
||||
package app.revanced.extension.shared.innertube.client
|
||||
|
||||
/**
|
||||
* Used to fetch video information.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object YouTubeWebClient {
|
||||
/**
|
||||
* This user agent does not require a PoToken in [ClientType.MWEB]
|
||||
* https://github.com/yt-dlp/yt-dlp/blob/0b6b7742c2e7f2a1fcb0b54ef3dd484bab404b3f/yt_dlp/extractor/youtube.py#L259
|
||||
*/
|
||||
private const val USER_AGENT_SAFARI =
|
||||
"Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)"
|
||||
|
||||
@ -26,11 +22,11 @@ object YouTubeWebClient {
|
||||
* Client version.
|
||||
*/
|
||||
@JvmField
|
||||
val clientVersion: String
|
||||
val clientVersion: String,
|
||||
) {
|
||||
MWEB(
|
||||
id = 2,
|
||||
clientVersion = "2.20241202.07.00"
|
||||
clientVersion = "2.20241202.07.00",
|
||||
),
|
||||
WEB_REMIX(
|
||||
id = 29,
|
@ -1,9 +1,8 @@
|
||||
package app.revanced.extension.shared.patches.spoof.requests
|
||||
package app.revanced.extension.shared.innertube.requests
|
||||
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.requests.Route
|
||||
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
@ -21,96 +20,17 @@ import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
@Suppress("deprecation")
|
||||
object PlayerRoutes {
|
||||
@JvmField
|
||||
val CREATE_PLAYLIST: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"playlist/create" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=playlistId"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val DELETE_PLAYLIST: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"playlist/delete" +
|
||||
"?prettyPrint=false"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val EDIT_PLAYLIST: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"browse/edit_playlist" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=status," +
|
||||
"playlistEditResults"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_PLAYLISTS: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"playlist/get_add_to_playlist" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=contents.addToPlaylistRenderer.playlists.playlistAddToOptionRenderer"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_CATEGORY: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=microformat.playerMicroformatRenderer.category"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_SET_VIDEO_ID: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"next" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=contents.singleColumnWatchNextResults." +
|
||||
"playlist.playlist.contents.playlistPanelVideoRenderer." +
|
||||
"playlistSetVideoId"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_PLAYLIST_PAGE: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"next" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=contents.singleColumnWatchNextResults.playlist.playlist"
|
||||
).compile()
|
||||
|
||||
@JvmField
|
||||
val GET_STREAMING_DATA: CompiledRoute = Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?fields=streamingData" +
|
||||
"&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,
|
||||
"player" +
|
||||
"?prettyPrint=false" +
|
||||
"&fields=videoDetails.channelId," +
|
||||
"videoDetails.isLiveContent," +
|
||||
"videoDetails.isUpcoming"
|
||||
).compile()
|
||||
object InnerTubeRequestBody {
|
||||
|
||||
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
||||
|
||||
private const val AUTHORIZATION_HEADER = "Authorization"
|
||||
private val REQUEST_HEADER_KEYS = setOf(
|
||||
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
"X-Goog-Visitor-Id"
|
||||
)
|
||||
|
||||
/**
|
||||
* TCP connection and HTTP read timeout
|
||||
*/
|
||||
@ -230,16 +150,10 @@ object PlayerRoutes {
|
||||
client.put("osName", clientType.osName)
|
||||
client.put("osVersion", clientType.osVersion)
|
||||
client.put("androidSdkVersion", clientType.androidSdkVersion)
|
||||
if (clientType.gmscoreVersionCode != null) {
|
||||
client.put("gmscoreVersionCode", clientType.gmscoreVersionCode)
|
||||
}
|
||||
client.put(
|
||||
"hl",
|
||||
LOCALE_LANGUAGE
|
||||
)
|
||||
client.put("hl", LOCALE_LANGUAGE)
|
||||
client.put("gl", LOCALE_COUNTRY)
|
||||
client.put("timeZone", TIME_ZONE_ID)
|
||||
client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
|
||||
client.put("utcOffsetMinutes", UTC_OFFSET_MINUTES.toString())
|
||||
|
||||
val context = JSONObject()
|
||||
context.put("client", client)
|
||||
@ -269,7 +183,7 @@ object PlayerRoutes {
|
||||
videoIds.put(0, videoId)
|
||||
innerTubeBody.put("videoIds", videoIds)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException({ "Failed to create playlist innerTubeBody" }, e)
|
||||
Logger.printException({ "Failed to create create/playlist innerTubeBody" }, e)
|
||||
}
|
||||
|
||||
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
@ -284,7 +198,7 @@ object PlayerRoutes {
|
||||
try {
|
||||
innerTubeBody.put("playlistId", playlistId)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException({ "Failed to create playlist innerTubeBody" }, e)
|
||||
Logger.printException({ "Failed to create delete/playlist innerTubeBody" }, e)
|
||||
}
|
||||
|
||||
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
@ -314,7 +228,7 @@ object PlayerRoutes {
|
||||
actionsArray.put(0, actionsObject)
|
||||
innerTubeBody.put("actions", actionsArray)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException({ "Failed to create playlist innerTubeBody" }, e)
|
||||
Logger.printException({ "Failed to create edit/playlist innerTubeBody" }, e)
|
||||
}
|
||||
|
||||
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
@ -330,7 +244,7 @@ object PlayerRoutes {
|
||||
innerTubeBody.put("playlistId", playlistId)
|
||||
innerTubeBody.put("excludeWatchLater", false)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException({ "Failed to create playlist innerTubeBody" }, e)
|
||||
Logger.printException({ "Failed to create get/playlists innerTubeBody" }, e)
|
||||
}
|
||||
|
||||
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
@ -354,44 +268,57 @@ object PlayerRoutes {
|
||||
actionsArray.put(0, actionsObject)
|
||||
innerTubeBody.put("actions", actionsArray)
|
||||
} catch (e: JSONException) {
|
||||
Logger.printException({ "Failed to create playlist innerTubeBody" }, e)
|
||||
Logger.printException({ "Failed to create save/playlist innerTubeBody" }, e)
|
||||
}
|
||||
|
||||
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlayerResponseConnectionFromRoute(
|
||||
fun getInnerTubeResponseConnectionFromRoute(
|
||||
route: CompiledRoute,
|
||||
clientType: YouTubeAppClient.ClientType
|
||||
): HttpURLConnection {
|
||||
return getPlayerResponseConnectionFromRoute(
|
||||
route,
|
||||
clientType.userAgent,
|
||||
clientType.id.toString(),
|
||||
clientType.clientVersion
|
||||
clientType: YouTubeAppClient.ClientType,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
) = getInnerTubeResponseConnectionFromRoute(
|
||||
route = route,
|
||||
userAgent = clientType.userAgent,
|
||||
clientId = clientType.id.toString(),
|
||||
clientVersion = clientType.clientVersion,
|
||||
supportsCookies = clientType.supportsCookies,
|
||||
requestHeader = requestHeader,
|
||||
connectTimeout = connectTimeout,
|
||||
readTimeout = readTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlayerResponseConnectionFromRoute(
|
||||
fun getInnerTubeResponseConnectionFromRoute(
|
||||
route: CompiledRoute,
|
||||
clientType: YouTubeWebClient.ClientType
|
||||
): HttpURLConnection {
|
||||
return getPlayerResponseConnectionFromRoute(
|
||||
route,
|
||||
clientType.userAgent,
|
||||
clientType.id.toString(),
|
||||
clientType.clientVersion,
|
||||
clientType: YouTubeWebClient.ClientType,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
) = getInnerTubeResponseConnectionFromRoute(
|
||||
route = route,
|
||||
userAgent = clientType.userAgent,
|
||||
clientId = clientType.id.toString(),
|
||||
clientVersion = clientType.clientVersion,
|
||||
requestHeader = requestHeader,
|
||||
connectTimeout = connectTimeout,
|
||||
readTimeout = readTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getPlayerResponseConnectionFromRoute(
|
||||
fun getInnerTubeResponseConnectionFromRoute(
|
||||
route: CompiledRoute,
|
||||
userAgent: String,
|
||||
clientId: String,
|
||||
clientVersion: String
|
||||
clientVersion: String,
|
||||
supportsCookies: Boolean = true,
|
||||
requestHeader: Map<String, String>? = null,
|
||||
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||
): HttpURLConnection {
|
||||
val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route)
|
||||
|
||||
@ -403,8 +330,24 @@ object PlayerRoutes {
|
||||
connection.useCaches = false
|
||||
connection.doOutput = true
|
||||
|
||||
connection.connectTimeout = CONNECTION_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = CONNECTION_TIMEOUT_MILLISECONDS
|
||||
connection.connectTimeout = connectTimeout
|
||||
connection.readTimeout = readTimeout
|
||||
|
||||
if (requestHeader != null) {
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = requestHeader[key]
|
||||
if (value != null) {
|
||||
if (key == AUTHORIZATION_HEADER) {
|
||||
if (!supportsCookies) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connection
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
package app.revanced.extension.shared.innertube.requests
|
||||
|
||||
import app.revanced.extension.shared.requests.Route
|
||||
import app.revanced.extension.shared.requests.Route.CompiledRoute
|
||||
|
||||
object InnerTubeRoutes {
|
||||
|
||||
@JvmField
|
||||
val CREATE_PLAYLIST = compileRoute(
|
||||
endpoint = "playlist/create",
|
||||
fields = "playlistId",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val DELETE_PLAYLIST = compileRoute(
|
||||
endpoint = "playlist/delete",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val EDIT_PLAYLIST = compileRoute(
|
||||
endpoint = "browse/edit_playlist",
|
||||
fields = "status," + "playlistEditResults",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_CATEGORY = compileRoute(
|
||||
endpoint = "player",
|
||||
fields = "microformat.playerMicroformatRenderer.category",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_PLAYLISTS = compileRoute(
|
||||
endpoint = "playlist/get_add_to_playlist",
|
||||
fields = "contents.addToPlaylistRenderer.playlists.playlistAddToOptionRenderer",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_SET_VIDEO_ID = compileRoute(
|
||||
endpoint = "next",
|
||||
fields = "contents.singleColumnWatchNextResults." +
|
||||
"playlist.playlist.contents.playlistPanelVideoRenderer." +
|
||||
"playlistSetVideoId",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_PLAYLIST_PAGE = compileRoute(
|
||||
endpoint = "next",
|
||||
fields = "contents.singleColumnWatchNextResults.playlist.playlist",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_STREAMING_DATA = compileRoute(
|
||||
endpoint = "player",
|
||||
fields = "streamingData",
|
||||
alt = "proto",
|
||||
prettier = true,
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_ACTION_BUTTON = compileRoute(
|
||||
endpoint = "next",
|
||||
fields = "contents.singleColumnWatchNextResults." +
|
||||
"results.results.contents.slimVideoMetadataSectionRenderer." +
|
||||
"contents.elementRenderer.newElement.type.componentType." +
|
||||
"model.videoActionBarModel.buttons.buttonViewModel"
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GET_VIDEO_DETAILS = compileRoute(
|
||||
endpoint = "player",
|
||||
fields = "videoDetails.channelId," +
|
||||
"videoDetails.isLiveContent," +
|
||||
"videoDetails.isUpcoming"
|
||||
)
|
||||
|
||||
private fun compileRoute(
|
||||
endpoint: String,
|
||||
fields: String? = null,
|
||||
alt: String? = null,
|
||||
prettier: Boolean = false,
|
||||
): CompiledRoute {
|
||||
var query = Array<String>(4) { "&" }
|
||||
var i = 0
|
||||
query[i] = "?"
|
||||
|
||||
val sb = StringBuilder(endpoint)
|
||||
if (prettier == false) {
|
||||
sb.append(query[i++])
|
||||
sb.append("prettyPrint=false")
|
||||
}
|
||||
if (fields != null) {
|
||||
sb.append(query[i++])
|
||||
sb.append("fields=")
|
||||
sb.append(fields)
|
||||
}
|
||||
if (alt != null) {
|
||||
sb.append(query[i++])
|
||||
sb.append("alt=")
|
||||
sb.append(alt)
|
||||
}
|
||||
|
||||
return Route(
|
||||
Route.Method.POST,
|
||||
sb.toString()
|
||||
).compile()
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package app.revanced.extension.shared.patches.spoof;
|
||||
|
||||
import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeMusicAppClient.ClientType;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofClientPatch extends BlockRequestPatch {
|
||||
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
|
||||
private static final ClientType CLIENT_TYPE = BaseSettings.SPOOF_CLIENT_TYPE.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
|
@ -10,7 +10,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient.ClientType;
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeAppClient.ClientType;
|
||||
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
@ -19,10 +19,6 @@ import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
private static final String PO_TOKEN =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
|
||||
private static final String VISITOR_DATA =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_VISITOR_DATA.get();
|
||||
private static final boolean SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION =
|
||||
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION.get();
|
||||
|
||||
@ -79,7 +75,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
|
||||
public static void fetchStreams(String url, Map<String, String> requestHeader) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
String id = Utils.getVideoIdFromRequest(url);
|
||||
if (id == null) {
|
||||
@ -89,7 +85,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
||||
return;
|
||||
}
|
||||
|
||||
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN);
|
||||
StreamingDataRequest.fetchRequest(id, requestHeader);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
package app.revanced.extension.shared.patches.spoof.requests
|
||||
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_STREAMING_DATA
|
||||
import app.revanced.extension.shared.settings.BaseSettings
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
@ -32,21 +31,19 @@ import java.util.concurrent.TimeoutException
|
||||
* did use its own client streams.
|
||||
*/
|
||||
class StreamingDataRequest private constructor(
|
||||
videoId: String, playerHeaders: Map<String, String>,
|
||||
visitorId: String, botGuardPoToken: String
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val videoId: String
|
||||
private val future: Future<ByteBuffer?>
|
||||
|
||||
init {
|
||||
Objects.requireNonNull(playerHeaders)
|
||||
Objects.requireNonNull(requestHeader)
|
||||
this.videoId = videoId
|
||||
this.future = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
videoId,
|
||||
playerHeaders,
|
||||
visitorId,
|
||||
botGuardPoToken
|
||||
requestHeader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -86,33 +83,16 @@ class StreamingDataRequest private constructor(
|
||||
|
||||
companion object {
|
||||
private const val AUTHORIZATION_HEADER = "Authorization"
|
||||
private const val VISITOR_ID_HEADER = "X-Goog-Visitor-Id"
|
||||
private val REQUEST_HEADER_KEYS = arrayOf(
|
||||
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
||||
"X-GOOG-API-FORMAT-VERSION",
|
||||
VISITOR_ID_HEADER
|
||||
)
|
||||
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||
|
||||
private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType =
|
||||
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
||||
|
||||
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
|
||||
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||
|
||||
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
||||
SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
|
||||
|
||||
private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
private var lastSpoofedClientFriendlyName: String? = null
|
||||
|
||||
@GuardedBy("itself")
|
||||
val cache: MutableMap<String, StreamingDataRequest> = Collections.synchronizedMap(
|
||||
@ -126,22 +106,24 @@ class StreamingDataRequest private constructor(
|
||||
|
||||
@JvmStatic
|
||||
val lastSpoofedClientName: String
|
||||
get() = lastSpoofedClientType
|
||||
?.friendlyName
|
||||
?: "Unknown"
|
||||
get() {
|
||||
return if (lastSpoofedClientFriendlyName != null) {
|
||||
lastSpoofedClientFriendlyName!!
|
||||
} else {
|
||||
"Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fetchRequest(
|
||||
videoId: String, fetchHeaders: Map<String, String>,
|
||||
visitorId: String, botGuardPoToken: String
|
||||
videoId: String,
|
||||
fetchHeaders: Map<String, String>,
|
||||
) {
|
||||
// Always fetch, even if there is an existing request for the same video.
|
||||
cache[videoId] =
|
||||
StreamingDataRequest(
|
||||
videoId,
|
||||
fetchHeaders,
|
||||
visitorId,
|
||||
botGuardPoToken
|
||||
fetchHeaders
|
||||
)
|
||||
}
|
||||
|
||||
@ -157,64 +139,28 @@ class StreamingDataRequest private constructor(
|
||||
private fun send(
|
||||
clientType: YouTubeAppClient.ClientType,
|
||||
videoId: String,
|
||||
playerHeaders: Map<String, String>,
|
||||
visitorId: String,
|
||||
botGuardPoToken: String
|
||||
requestHeader: Map<String, String>,
|
||||
): HttpURLConnection? {
|
||||
Objects.requireNonNull(clientType)
|
||||
Objects.requireNonNull(videoId)
|
||||
Objects.requireNonNull(playerHeaders)
|
||||
Objects.requireNonNull(requestHeader)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" }
|
||||
|
||||
try {
|
||||
val connection =
|
||||
getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
val usePoToken =
|
||||
clientType.requirePoToken && !StringUtils.isAnyEmpty(botGuardPoToken, visitorId)
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
if (key == AUTHORIZATION_HEADER) {
|
||||
if (!clientType.supportsCookies) {
|
||||
Logger.printDebug { "Not including request header: $key" }
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (key == VISITOR_ID_HEADER && usePoToken) {
|
||||
val originalVisitorId: String = value
|
||||
Logger.printDebug { "Original visitor id:\n$originalVisitorId" }
|
||||
Logger.printDebug { "Replaced visitor id:\n$visitorId" }
|
||||
value = visitorId
|
||||
}
|
||||
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody: ByteArray
|
||||
if (usePoToken) {
|
||||
requestBody = createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
botGuardPoToken = botGuardPoToken,
|
||||
visitorId = visitorId,
|
||||
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
||||
getInnerTubeResponseConnectionFromRoute(
|
||||
GET_STREAMING_DATA,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" }
|
||||
} else {
|
||||
requestBody =
|
||||
createApplicationRequestBody(
|
||||
|
||||
val requestBody = createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
||||
)
|
||||
}
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -243,15 +189,15 @@ class StreamingDataRequest private constructor(
|
||||
}
|
||||
|
||||
private fun fetch(
|
||||
videoId: String, playerHeaders: Map<String, String>,
|
||||
visitorId: String, botGuardPoToken: String
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>,
|
||||
): ByteBuffer? {
|
||||
lastSpoofedClientType = null
|
||||
lastSpoofedClientFriendlyName = null
|
||||
|
||||
// Retry with different client if empty response body is received.
|
||||
for (clientType in CLIENT_ORDER_TO_USE) {
|
||||
if (clientType.requireAuth &&
|
||||
playerHeaders[AUTHORIZATION_HEADER] == null
|
||||
requestHeader[AUTHORIZATION_HEADER] == null
|
||||
) {
|
||||
Logger.printDebug { "Skipped login-required client (incognito mode or not logged in)\nClient: $clientType\nVideo: $videoId" }
|
||||
continue
|
||||
@ -259,9 +205,7 @@ class StreamingDataRequest private constructor(
|
||||
send(
|
||||
clientType,
|
||||
videoId,
|
||||
playerHeaders,
|
||||
visitorId,
|
||||
botGuardPoToken
|
||||
requestHeader,
|
||||
)?.let { connection ->
|
||||
try {
|
||||
// gzip encoding doesn't response with content length (-1),
|
||||
@ -271,14 +215,14 @@ class StreamingDataRequest private constructor(
|
||||
} else {
|
||||
BufferedInputStream(connection.inputStream).use { inputStream ->
|
||||
ByteArrayOutputStream().use { stream ->
|
||||
val buffer = ByteArray(2048)
|
||||
val buffer = ByteArray(4096)
|
||||
var bytesRead: Int
|
||||
while ((inputStream.read(buffer)
|
||||
.also { bytesRead = it }) >= 0
|
||||
) {
|
||||
stream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
lastSpoofedClientType = clientType
|
||||
lastSpoofedClientFriendlyName = clientType.friendlyName
|
||||
return ByteBuffer.wrap(stream.toByteArray())
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ package app.revanced.extension.shared.settings;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeAppClient;
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeMusicAppClient;
|
||||
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
|
||||
import app.revanced.extension.shared.patches.WatchHistoryPatch.WatchHistoryType;
|
||||
import app.revanced.extension.shared.patches.client.MusicAppClient;
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient;
|
||||
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ public class BaseSettings {
|
||||
* Some patches are in a shared path, so they are declared here.
|
||||
*/
|
||||
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true);
|
||||
public static final EnumSetting<MusicAppClient.ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", MusicAppClient.ClientType.IOS_MUSIC_6_21, true);
|
||||
public static final EnumSetting<YouTubeMusicAppClient.ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", YouTubeMusicAppClient.ClientType.IOS_MUSIC_6_21, true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube.
|
||||
@ -44,10 +44,7 @@ public class BaseSettings {
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION = new BooleanSetting("revanced_spoof_streaming_data_skip_response_encryption", TRUE, true);
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
|
||||
// Client type must be last spoof setting due to cyclic references.
|
||||
public static final EnumSetting<YouTubeAppClient.ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_UNPLUGGED, true);
|
||||
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
|
||||
public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
|
||||
public static final EnumSetting<YouTubeAppClient.ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_VR, true);
|
||||
|
||||
/**
|
||||
* These settings are used by YouTube and YouTube Music.
|
||||
|
@ -2,8 +2,10 @@ package app.revanced.extension.youtube.patches.general.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createWebInnertubeBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_VIDEO_DETAILS
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
@ -86,12 +88,11 @@ class VideoDetailsRequest private constructor(
|
||||
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_VIDEO_DETAILS,
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_VIDEO_DETAILS,
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
||||
val requestBody = createWebInnertubeBody(clientType, videoId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
@ -1,8 +1,10 @@
|
||||
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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_VIDEO_ACTION_BUTTON
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
@ -20,10 +22,10 @@ import java.util.concurrent.TimeoutException
|
||||
|
||||
class ActionButtonRequest private constructor(
|
||||
private val videoId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Array<ActionButton>> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId, playerHeaders)
|
||||
fetch(videoId, requestHeader)
|
||||
}
|
||||
|
||||
val array: Array<ActionButton>
|
||||
@ -52,14 +54,6 @@ class ActionButtonRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -73,11 +67,11 @@ class ActionButtonRequest private constructor(
|
||||
})
|
||||
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
|
||||
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(videoId)) {
|
||||
cache[videoId] = ActionButtonRequest(videoId, playerHeaders)
|
||||
cache[videoId] = ActionButtonRequest(videoId, requestHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,40 +87,25 @@ class ActionButtonRequest private constructor(
|
||||
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? {
|
||||
private fun sendRequest(videoId: String, requestHeader: Map<String, String>): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// '/next' request does not require PoToken.
|
||||
// '/next' endpoint 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 connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_VIDEO_ACTION_BUTTON,
|
||||
clientType,
|
||||
requestHeader,
|
||||
)
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
val requestBody = createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId
|
||||
)
|
||||
@ -214,8 +193,11 @@ class ActionButtonRequest private constructor(
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Array<ActionButton> {
|
||||
val json = sendRequest(videoId, playerHeaders)
|
||||
private fun fetch(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>
|
||||
): Array<ActionButton> {
|
||||
val json = sendRequest(videoId, requestHeader)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
package app.revanced.extension.youtube.patches.utils.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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createPlaylistRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.CREATE_PLAYLIST
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_SET_VIDEO_ID
|
||||
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.utils.requests.CreatePlaylistRequest.Companion.HTTP_TIMEOUT_MILLISECONDS
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
@ -20,10 +23,10 @@ import java.util.concurrent.TimeoutException
|
||||
|
||||
class CreatePlaylistRequest private constructor(
|
||||
private val videoId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Pair<String, String>> = Utils.submitOnBackgroundThread {
|
||||
fetch(videoId, playerHeaders)
|
||||
fetch(videoId, requestHeader)
|
||||
}
|
||||
|
||||
val playlistId: Pair<String, String>?
|
||||
@ -52,14 +55,6 @@ class CreatePlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -80,11 +75,11 @@ class CreatePlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
|
||||
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(videoId)) {
|
||||
cache[videoId] = CreatePlaylistRequest(videoId, playerHeaders)
|
||||
cache[videoId] = CreatePlaylistRequest(videoId, requestHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,40 +95,26 @@ class CreatePlaylistRequest private constructor(
|
||||
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 sendCreatePlaylistRequest(videoId: String, playerHeaders: Map<String, String>): JSONObject? {
|
||||
private fun sendCreatePlaylistRequest(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'playlist/create' request does not require PoToken.
|
||||
// 'playlist/create' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching create playlist request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.CREATE_PLAYLIST,
|
||||
clientType
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
CREATE_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader,
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.createPlaylistRequestBody(
|
||||
videoId = videoId
|
||||
)
|
||||
val requestBody = createPlaylistRequestBody(videoId = videoId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -159,32 +140,27 @@ class CreatePlaylistRequest private constructor(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun sendSetVideoIdRequest(videoId: String, playlistId: String, playerHeaders: Map<String, String>): JSONObject? {
|
||||
private fun sendSetVideoIdRequest(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'playlist/create' request does not require PoToken.
|
||||
// 'playlist/create' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching set video id request for: $playlistId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_SET_VIDEO_ID,
|
||||
clientType
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_SET_VIDEO_ID,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
val requestBody = createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
playlistId = playlistId
|
||||
@ -254,12 +230,15 @@ class CreatePlaylistRequest private constructor(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Pair<String, String>? {
|
||||
val createPlaylistJson = sendCreatePlaylistRequest(videoId, playerHeaders)
|
||||
private fun fetch(
|
||||
videoId: String,
|
||||
requestHeader: Map<String, String>
|
||||
): Pair<String, String>? {
|
||||
val createPlaylistJson = sendCreatePlaylistRequest(videoId, requestHeader)
|
||||
if (createPlaylistJson != null) {
|
||||
val playlistId = parseCreatePlaylistResponse(createPlaylistJson)
|
||||
if (playlistId != null) {
|
||||
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, playerHeaders)
|
||||
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, requestHeader)
|
||||
if (setVideoIdJson != null) {
|
||||
val setVideoId = parseSetVideoIdResponse(setVideoIdJson)
|
||||
if (setVideoId != null) {
|
||||
|
@ -1,12 +1,13 @@
|
||||
package app.revanced.extension.youtube.patches.utils.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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.deletePlaylistRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.DELETE_PLAYLIST
|
||||
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.utils.requests.DeletePlaylistRequest.Companion.HTTP_TIMEOUT_MILLISECONDS
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
@ -20,12 +21,12 @@ import java.util.concurrent.TimeoutException
|
||||
|
||||
class DeletePlaylistRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
playerHeaders,
|
||||
requestHeader,
|
||||
)
|
||||
}
|
||||
|
||||
@ -55,14 +56,6 @@ class DeletePlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -85,14 +78,14 @@ class DeletePlaylistRequest private constructor(
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(playlistId)) {
|
||||
cache[playlistId] = DeletePlaylistRequest(
|
||||
playlistId,
|
||||
playerHeaders
|
||||
requestHeader
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -109,40 +102,26 @@ class DeletePlaylistRequest private constructor(
|
||||
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(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'playlist/delete' request does not require PoToken.
|
||||
// 'playlist/delete' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching delete playlist request, playlistId: $playlistId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.DELETE_PLAYLIST,
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
DELETE_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody = PlayerRoutes.deletePlaylistRequestBody(playlistId)
|
||||
val requestBody = deletePlaylistRequestBody(playlistId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -184,9 +163,9 @@ class DeletePlaylistRequest private constructor(
|
||||
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): Boolean? {
|
||||
val json = sendRequest(playlistId, playerHeaders)
|
||||
val json = sendRequest(playlistId, requestHeader)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package app.revanced.extension.youtube.patches.utils.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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.editPlaylistRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLAYLIST
|
||||
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.utils.requests.EditPlaylistRequest.Companion.HTTP_TIMEOUT_MILLISECONDS
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
@ -23,14 +24,14 @@ class EditPlaylistRequest private constructor(
|
||||
private val videoId: String,
|
||||
private val playlistId: String,
|
||||
private val setVideoId: String?,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
videoId,
|
||||
playlistId,
|
||||
setVideoId,
|
||||
playerHeaders,
|
||||
requestHeader,
|
||||
)
|
||||
}
|
||||
|
||||
@ -60,14 +61,6 @@ class EditPlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -99,7 +92,7 @@ class EditPlaylistRequest private constructor(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
) {
|
||||
Objects.requireNonNull(videoId)
|
||||
synchronized(cache) {
|
||||
@ -108,7 +101,7 @@ class EditPlaylistRequest private constructor(
|
||||
videoId,
|
||||
playlistId,
|
||||
setVideoId,
|
||||
playerHeaders
|
||||
requestHeader
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -125,46 +118,31 @@ class EditPlaylistRequest private constructor(
|
||||
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,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(videoId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'browse/edit_playlist' request does not require PoToken.
|
||||
// 'browse/edit_playlist' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching edit playlist request, videoId: $videoId, playlistId: $playlistId, setVideoId: $setVideoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.EDIT_PLAYLIST,
|
||||
clientType
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
EDIT_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.editPlaylistRequestBody(
|
||||
val requestBody = editPlaylistRequestBody(
|
||||
videoId = videoId,
|
||||
playlistId = playlistId,
|
||||
setVideoId = setVideoId,
|
||||
setVideoId = setVideoId
|
||||
)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
@ -197,7 +175,8 @@ class EditPlaylistRequest private constructor(
|
||||
if (remove) {
|
||||
return ""
|
||||
}
|
||||
val playlistEditResultsJSONObject = json.getJSONArray("playlistEditResults").get(0)
|
||||
val playlistEditResultsJSONObject =
|
||||
json.getJSONArray("playlistEditResults").get(0)
|
||||
|
||||
if (playlistEditResultsJSONObject is JSONObject) {
|
||||
return playlistEditResultsJSONObject
|
||||
@ -220,9 +199,9 @@ class EditPlaylistRequest private constructor(
|
||||
videoId: String,
|
||||
playlistId: String,
|
||||
setVideoId: String?,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): String? {
|
||||
val json = sendRequest(videoId, playlistId, setVideoId, playerHeaders)
|
||||
val json = sendRequest(videoId, playlistId, setVideoId, requestHeader)
|
||||
if (json != null) {
|
||||
return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package app.revanced.extension.youtube.patches.utils.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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getPlaylistsRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLISTS
|
||||
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.utils.requests.GetPlaylistsRequest.Companion.HTTP_TIMEOUT_MILLISECONDS
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
@ -20,12 +21,12 @@ import java.util.concurrent.TimeoutException
|
||||
|
||||
class GetPlaylistsRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Array<Pair<String, String>>> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
playerHeaders,
|
||||
requestHeader,
|
||||
)
|
||||
}
|
||||
|
||||
@ -55,14 +56,6 @@ class GetPlaylistsRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -85,14 +78,14 @@ class GetPlaylistsRequest private constructor(
|
||||
@JvmStatic
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
if (!cache.containsKey(playlistId)) {
|
||||
cache[playlistId] = GetPlaylistsRequest(
|
||||
playlistId,
|
||||
playerHeaders
|
||||
requestHeader
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -109,40 +102,26 @@ class GetPlaylistsRequest private constructor(
|
||||
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(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'playlist/get_add_to_playlist' request does not require PoToken.
|
||||
// 'playlist/get_add_to_playlist' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching get playlists request, playlistId: $playlistId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_PLAYLISTS,
|
||||
clientType
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_PLAYLISTS,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody = PlayerRoutes.getPlaylistsRequestBody(playlistId)
|
||||
val requestBody = getPlaylistsRequestBody(playlistId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -223,9 +202,9 @@ class GetPlaylistsRequest private constructor(
|
||||
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): Array<Pair<String, String>>? {
|
||||
val json = sendRequest(playlistId, playerHeaders)
|
||||
val json = sendRequest(playlistId, requestHeader)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package app.revanced.extension.youtube.patches.utils.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.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.savePlaylistRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLAYLIST
|
||||
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.utils.requests.SavePlaylistRequest.Companion.HTTP_TIMEOUT_MILLISECONDS
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
@ -21,13 +22,13 @@ import java.util.concurrent.TimeoutException
|
||||
class SavePlaylistRequest private constructor(
|
||||
private val playlistId: String,
|
||||
private val libraryId: String,
|
||||
private val playerHeaders: Map<String, String>,
|
||||
private val requestHeader: Map<String, String>,
|
||||
) {
|
||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||
fetch(
|
||||
playlistId,
|
||||
libraryId,
|
||||
playerHeaders,
|
||||
requestHeader,
|
||||
)
|
||||
}
|
||||
|
||||
@ -57,14 +58,6 @@ class SavePlaylistRequest private constructor(
|
||||
}
|
||||
|
||||
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")
|
||||
@ -88,14 +81,14 @@ class SavePlaylistRequest private constructor(
|
||||
fun fetchRequestIfNeeded(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
) {
|
||||
Objects.requireNonNull(playlistId)
|
||||
synchronized(cache) {
|
||||
cache[libraryId] = SavePlaylistRequest(
|
||||
playlistId,
|
||||
libraryId,
|
||||
playerHeaders
|
||||
requestHeader
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -111,43 +104,28 @@ class SavePlaylistRequest private constructor(
|
||||
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(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): JSONObject? {
|
||||
Objects.requireNonNull(playlistId)
|
||||
Objects.requireNonNull(libraryId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
// 'browse/edit_playlist' request does not require PoToken.
|
||||
// 'browse/edit_playlist' endpoint does not require PoToken.
|
||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||
val clientTypeName = clientType.name
|
||||
Logger.printDebug { "Fetching edit playlist request, playlistId: $playlistId, libraryId: $libraryId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.EDIT_PLAYLIST,
|
||||
clientType
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
EDIT_PLAYLIST,
|
||||
clientType,
|
||||
requestHeader
|
||||
)
|
||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
||||
|
||||
for (key in REQUEST_HEADER_KEYS) {
|
||||
var value = playerHeaders[key]
|
||||
if (value != null) {
|
||||
connection.setRequestProperty(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
val requestBody =
|
||||
PlayerRoutes.savePlaylistRequestBody(libraryId, playlistId)
|
||||
val requestBody = savePlaylistRequestBody(libraryId, playlistId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
@ -190,9 +168,9 @@ class SavePlaylistRequest private constructor(
|
||||
private fun fetch(
|
||||
playlistId: String,
|
||||
libraryId: String,
|
||||
playerHeaders: Map<String, String>
|
||||
requestHeader: Map<String, String>
|
||||
): Boolean? {
|
||||
val json = sendRequest(playlistId, libraryId,playerHeaders)
|
||||
val json = sendRequest(playlistId, libraryId, requestHeader)
|
||||
if (json != null) {
|
||||
return parseResponse(json)
|
||||
}
|
||||
|
@ -2,9 +2,13 @@ package app.revanced.extension.youtube.patches.video.requests
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.annotation.GuardedBy
|
||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createWebInnertubeBody
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_CATEGORY
|
||||
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLIST_PAGE
|
||||
import app.revanced.extension.shared.requests.Requester
|
||||
import app.revanced.extension.shared.utils.Logger
|
||||
import app.revanced.extension.shared.utils.Utils
|
||||
@ -124,12 +128,12 @@ class MusicRequest private constructor(
|
||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_PLAYLIST_PAGE,
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_PLAYLIST_PAGE,
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createApplicationRequestBody(
|
||||
createApplicationRequestBody(
|
||||
clientType = clientType,
|
||||
videoId = videoId,
|
||||
playlistId = "RD$videoId"
|
||||
@ -168,12 +172,11 @@ class MusicRequest private constructor(
|
||||
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
||||
|
||||
try {
|
||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||
PlayerRoutes.GET_CATEGORY,
|
||||
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||
GET_CATEGORY,
|
||||
clientType
|
||||
)
|
||||
val requestBody =
|
||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
||||
val requestBody = createWebInnertubeBody(clientType, videoId)
|
||||
|
||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||
connection.outputStream.write(requestBody)
|
||||
|
Loading…
x
Reference in New Issue
Block a user