mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-01 07:04: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 android.annotation.SuppressLint
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.settings.AppLanguage
|
import app.revanced.extension.shared.settings.AppLanguage
|
||||||
import app.revanced.extension.shared.utils.Logger
|
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" }
|
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_PLAYLIST_PAGE,
|
GET_PLAYLIST_PAGE,
|
||||||
clientType
|
clientType
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For some reason, the tracks in Top Songs have the playlistId of the album:
|
* 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)
|
* [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.
|
* So we can work around this by setting the language to English when sending the request.
|
||||||
*/
|
*/
|
||||||
val requestBody =
|
val requestBody =
|
||||||
PlayerRoutes.createApplicationRequestBody(
|
createApplicationRequestBody(
|
||||||
clientType = clientType,
|
clientType = clientType,
|
||||||
videoId = videoId,
|
videoId = videoId,
|
||||||
playlistId = playlistId,
|
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 android.os.Build
|
||||||
import app.revanced.extension.shared.settings.BaseSettings
|
import app.revanced.extension.shared.settings.BaseSettings
|
||||||
@ -11,24 +11,6 @@ import java.util.Locale
|
|||||||
*/
|
*/
|
||||||
object YouTubeAppClient {
|
object YouTubeAppClient {
|
||||||
// IOS
|
// 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 DEVICE_MAKE_IOS = "Apple"
|
||||||
private const val OS_NAME_IOS = "iOS"
|
private const val OS_NAME_IOS = "iOS"
|
||||||
|
|
||||||
@ -49,7 +31,6 @@ object YouTubeAppClient {
|
|||||||
"13_7"
|
"13_7"
|
||||||
else
|
else
|
||||||
"18_3_2"
|
"18_3_2"
|
||||||
private val USER_AGENT_IOS = iOSUserAgent(PACKAGE_NAME_IOS, CLIENT_VERSION_IOS)
|
|
||||||
|
|
||||||
|
|
||||||
// IOS UNPLUGGED
|
// 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)'
|
* 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.
|
* Source: https://github.com/mitmproxy/mitmproxy/issues/4836.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("SameParameterValue")
|
||||||
private fun iOSUserAgent(
|
private fun iOSUserAgent(
|
||||||
packageName: String,
|
packageName: String,
|
||||||
clientVersion: String
|
clientVersion: String
|
||||||
@ -278,10 +260,6 @@ object YouTubeAppClient {
|
|||||||
* If true, 'Authorization' must be included.
|
* If true, 'Authorization' must be included.
|
||||||
*/
|
*/
|
||||||
val requireAuth: Boolean = false,
|
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.
|
* Client name for innertube body.
|
||||||
*/
|
*/
|
||||||
@ -362,22 +340,6 @@ object YouTubeAppClient {
|
|||||||
"iOS TV Force AVC"
|
"iOS TV Force AVC"
|
||||||
else
|
else
|
||||||
"iOS TV"
|
"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 {
|
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 android.os.Build;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class MusicAppClient {
|
public class YouTubeMusicAppClient {
|
||||||
|
|
||||||
// Response to the '/next' request is 'Please update to continue using the app':
|
// Response to the '/next' request is 'Please update to continue using the app':
|
||||||
// https://github.com/inotia00/ReVanced_Extended/issues/2743
|
// 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 DEVICE_MAKE_IOS_MUSIC = "Apple";
|
||||||
private static final String OS_NAME_IOS_MUSIC = "iOS";
|
private static final String OS_NAME_IOS_MUSIC = "iOS";
|
||||||
|
|
||||||
private MusicAppClient() {
|
private YouTubeMusicAppClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String androidUserAgent(String clientVersion) {
|
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.
|
* Used to fetch video information.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object YouTubeWebClient {
|
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 =
|
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)"
|
"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.
|
* Client version.
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@JvmField
|
||||||
val clientVersion: String
|
val clientVersion: String,
|
||||||
) {
|
) {
|
||||||
MWEB(
|
MWEB(
|
||||||
id = 2,
|
id = 2,
|
||||||
clientVersion = "2.20241202.07.00"
|
clientVersion = "2.20241202.07.00",
|
||||||
),
|
),
|
||||||
WEB_REMIX(
|
WEB_REMIX(
|
||||||
id = 29,
|
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.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||||
import app.revanced.extension.shared.requests.Requester
|
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.requests.Route.CompiledRoute
|
||||||
import app.revanced.extension.shared.settings.BaseSettings
|
import app.revanced.extension.shared.settings.BaseSettings
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
@ -21,96 +20,17 @@ import java.util.Locale
|
|||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
object PlayerRoutes {
|
object InnerTubeRequestBody {
|
||||||
@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()
|
|
||||||
|
|
||||||
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
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
|
* TCP connection and HTTP read timeout
|
||||||
*/
|
*/
|
||||||
@ -230,16 +150,10 @@ object PlayerRoutes {
|
|||||||
client.put("osName", clientType.osName)
|
client.put("osName", clientType.osName)
|
||||||
client.put("osVersion", clientType.osVersion)
|
client.put("osVersion", clientType.osVersion)
|
||||||
client.put("androidSdkVersion", clientType.androidSdkVersion)
|
client.put("androidSdkVersion", clientType.androidSdkVersion)
|
||||||
if (clientType.gmscoreVersionCode != null) {
|
client.put("hl", LOCALE_LANGUAGE)
|
||||||
client.put("gmscoreVersionCode", clientType.gmscoreVersionCode)
|
|
||||||
}
|
|
||||||
client.put(
|
|
||||||
"hl",
|
|
||||||
LOCALE_LANGUAGE
|
|
||||||
)
|
|
||||||
client.put("gl", LOCALE_COUNTRY)
|
client.put("gl", LOCALE_COUNTRY)
|
||||||
client.put("timeZone", TIME_ZONE_ID)
|
client.put("timeZone", TIME_ZONE_ID)
|
||||||
client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES")
|
client.put("utcOffsetMinutes", UTC_OFFSET_MINUTES.toString())
|
||||||
|
|
||||||
val context = JSONObject()
|
val context = JSONObject()
|
||||||
context.put("client", client)
|
context.put("client", client)
|
||||||
@ -269,7 +183,7 @@ object PlayerRoutes {
|
|||||||
videoIds.put(0, videoId)
|
videoIds.put(0, videoId)
|
||||||
innerTubeBody.put("videoIds", videoIds)
|
innerTubeBody.put("videoIds", videoIds)
|
||||||
} catch (e: JSONException) {
|
} 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)
|
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
@ -284,7 +198,7 @@ object PlayerRoutes {
|
|||||||
try {
|
try {
|
||||||
innerTubeBody.put("playlistId", playlistId)
|
innerTubeBody.put("playlistId", playlistId)
|
||||||
} catch (e: JSONException) {
|
} 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)
|
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
@ -314,7 +228,7 @@ object PlayerRoutes {
|
|||||||
actionsArray.put(0, actionsObject)
|
actionsArray.put(0, actionsObject)
|
||||||
innerTubeBody.put("actions", actionsArray)
|
innerTubeBody.put("actions", actionsArray)
|
||||||
} catch (e: JSONException) {
|
} 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)
|
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
@ -330,7 +244,7 @@ object PlayerRoutes {
|
|||||||
innerTubeBody.put("playlistId", playlistId)
|
innerTubeBody.put("playlistId", playlistId)
|
||||||
innerTubeBody.put("excludeWatchLater", false)
|
innerTubeBody.put("excludeWatchLater", false)
|
||||||
} catch (e: JSONException) {
|
} 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)
|
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
@ -354,44 +268,57 @@ object PlayerRoutes {
|
|||||||
actionsArray.put(0, actionsObject)
|
actionsArray.put(0, actionsObject)
|
||||||
innerTubeBody.put("actions", actionsArray)
|
innerTubeBody.put("actions", actionsArray)
|
||||||
} catch (e: JSONException) {
|
} 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)
|
return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlayerResponseConnectionFromRoute(
|
fun getInnerTubeResponseConnectionFromRoute(
|
||||||
route: CompiledRoute,
|
route: CompiledRoute,
|
||||||
clientType: YouTubeAppClient.ClientType
|
clientType: YouTubeAppClient.ClientType,
|
||||||
): HttpURLConnection {
|
requestHeader: Map<String, String>? = null,
|
||||||
return getPlayerResponseConnectionFromRoute(
|
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||||
route,
|
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||||
clientType.userAgent,
|
) = getInnerTubeResponseConnectionFromRoute(
|
||||||
clientType.id.toString(),
|
route = route,
|
||||||
clientType.clientVersion
|
userAgent = clientType.userAgent,
|
||||||
)
|
clientId = clientType.id.toString(),
|
||||||
}
|
clientVersion = clientType.clientVersion,
|
||||||
|
supportsCookies = clientType.supportsCookies,
|
||||||
|
requestHeader = requestHeader,
|
||||||
|
connectTimeout = connectTimeout,
|
||||||
|
readTimeout = readTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlayerResponseConnectionFromRoute(
|
fun getInnerTubeResponseConnectionFromRoute(
|
||||||
route: CompiledRoute,
|
route: CompiledRoute,
|
||||||
clientType: YouTubeWebClient.ClientType
|
clientType: YouTubeWebClient.ClientType,
|
||||||
): HttpURLConnection {
|
requestHeader: Map<String, String>? = null,
|
||||||
return getPlayerResponseConnectionFromRoute(
|
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||||
route,
|
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
|
||||||
clientType.userAgent,
|
) = getInnerTubeResponseConnectionFromRoute(
|
||||||
clientType.id.toString(),
|
route = route,
|
||||||
clientType.clientVersion,
|
userAgent = clientType.userAgent,
|
||||||
)
|
clientId = clientType.id.toString(),
|
||||||
}
|
clientVersion = clientType.clientVersion,
|
||||||
|
requestHeader = requestHeader,
|
||||||
|
connectTimeout = connectTimeout,
|
||||||
|
readTimeout = readTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getPlayerResponseConnectionFromRoute(
|
fun getInnerTubeResponseConnectionFromRoute(
|
||||||
route: CompiledRoute,
|
route: CompiledRoute,
|
||||||
userAgent: String,
|
userAgent: String,
|
||||||
clientId: 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 {
|
): HttpURLConnection {
|
||||||
val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route)
|
val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route)
|
||||||
|
|
||||||
@ -403,8 +330,24 @@ object PlayerRoutes {
|
|||||||
connection.useCaches = false
|
connection.useCaches = false
|
||||||
connection.doOutput = true
|
connection.doOutput = true
|
||||||
|
|
||||||
connection.connectTimeout = CONNECTION_TIMEOUT_MILLISECONDS
|
connection.connectTimeout = connectTimeout
|
||||||
connection.readTimeout = CONNECTION_TIMEOUT_MILLISECONDS
|
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
|
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;
|
package app.revanced.extension.shared.patches.spoof;
|
||||||
|
|
||||||
import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType;
|
import app.revanced.extension.shared.innertube.client.YouTubeMusicAppClient.ClientType;
|
||||||
import app.revanced.extension.music.settings.Settings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofClientPatch extends BlockRequestPatch {
|
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.
|
* Injection point.
|
||||||
|
@ -10,7 +10,7 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
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.patches.spoof.requests.StreamingDataRequest;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
@ -19,10 +19,6 @@ import app.revanced.extension.shared.utils.Utils;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
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 =
|
private static final boolean SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION =
|
||||||
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION.get();
|
SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION.get();
|
||||||
|
|
||||||
@ -79,7 +75,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* 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) {
|
if (SPOOF_STREAMING_DATA) {
|
||||||
String id = Utils.getVideoIdFromRequest(url);
|
String id = Utils.getVideoIdFromRequest(url);
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
@ -89,7 +85,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch {
|
|||||||
return;
|
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
|
package app.revanced.extension.shared.patches.spoof.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
|
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.createApplicationRequestBody
|
import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
|
import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_STREAMING_DATA
|
||||||
import app.revanced.extension.shared.settings.BaseSettings
|
import app.revanced.extension.shared.settings.BaseSettings
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
import app.revanced.extension.shared.utils.Utils
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -32,21 +31,19 @@ import java.util.concurrent.TimeoutException
|
|||||||
* did use its own client streams.
|
* did use its own client streams.
|
||||||
*/
|
*/
|
||||||
class StreamingDataRequest private constructor(
|
class StreamingDataRequest private constructor(
|
||||||
videoId: String, playerHeaders: Map<String, String>,
|
videoId: String,
|
||||||
visitorId: String, botGuardPoToken: String
|
requestHeader: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
private val videoId: String
|
private val videoId: String
|
||||||
private val future: Future<ByteBuffer?>
|
private val future: Future<ByteBuffer?>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Objects.requireNonNull(playerHeaders)
|
Objects.requireNonNull(requestHeader)
|
||||||
this.videoId = videoId
|
this.videoId = videoId
|
||||||
this.future = Utils.submitOnBackgroundThread {
|
this.future = Utils.submitOnBackgroundThread {
|
||||||
fetch(
|
fetch(
|
||||||
videoId,
|
videoId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
visitorId,
|
|
||||||
botGuardPoToken
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,33 +83,16 @@ class StreamingDataRequest private constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val AUTHORIZATION_HEADER = "Authorization"
|
private const val AUTHORIZATION_HEADER = "Authorization"
|
||||||
private const val VISITOR_ID_HEADER = "X-Goog-Visitor-Id"
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
private val REQUEST_HEADER_KEYS = arrayOf(
|
|
||||||
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
|
||||||
"X-GOOG-API-FORMAT-VERSION",
|
|
||||||
VISITOR_ID_HEADER
|
|
||||||
)
|
|
||||||
private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType =
|
private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType =
|
||||||
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
|
||||||
|
|
||||||
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
|
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
|
||||||
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
|
||||||
|
|
||||||
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
|
||||||
SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
|
SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
|
||||||
|
|
||||||
private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null
|
private var lastSpoofedClientFriendlyName: String? = 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
|
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
val cache: MutableMap<String, StreamingDataRequest> = Collections.synchronizedMap(
|
val cache: MutableMap<String, StreamingDataRequest> = Collections.synchronizedMap(
|
||||||
@ -126,22 +106,24 @@ class StreamingDataRequest private constructor(
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val lastSpoofedClientName: String
|
val lastSpoofedClientName: String
|
||||||
get() = lastSpoofedClientType
|
get() {
|
||||||
?.friendlyName
|
return if (lastSpoofedClientFriendlyName != null) {
|
||||||
?: "Unknown"
|
lastSpoofedClientFriendlyName!!
|
||||||
|
} else {
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fetchRequest(
|
fun fetchRequest(
|
||||||
videoId: String, fetchHeaders: Map<String, String>,
|
videoId: String,
|
||||||
visitorId: String, botGuardPoToken: String
|
fetchHeaders: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
// Always fetch, even if there is an existing request for the same video.
|
// Always fetch, even if there is an existing request for the same video.
|
||||||
cache[videoId] =
|
cache[videoId] =
|
||||||
StreamingDataRequest(
|
StreamingDataRequest(
|
||||||
videoId,
|
videoId,
|
||||||
fetchHeaders,
|
fetchHeaders
|
||||||
visitorId,
|
|
||||||
botGuardPoToken
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,64 +139,28 @@ class StreamingDataRequest private constructor(
|
|||||||
private fun send(
|
private fun send(
|
||||||
clientType: YouTubeAppClient.ClientType,
|
clientType: YouTubeAppClient.ClientType,
|
||||||
videoId: String,
|
videoId: String,
|
||||||
playerHeaders: Map<String, String>,
|
requestHeader: Map<String, String>,
|
||||||
visitorId: String,
|
|
||||||
botGuardPoToken: String
|
|
||||||
): HttpURLConnection? {
|
): HttpURLConnection? {
|
||||||
Objects.requireNonNull(clientType)
|
Objects.requireNonNull(clientType)
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
Objects.requireNonNull(playerHeaders)
|
Objects.requireNonNull(requestHeader)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" }
|
Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection =
|
val connection =
|
||||||
getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType)
|
getInnerTubeResponseConnectionFromRoute(
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
GET_STREAMING_DATA,
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
clientType,
|
||||||
|
requestHeader
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" }
|
|
||||||
} else {
|
val requestBody = createApplicationRequestBody(
|
||||||
requestBody =
|
clientType = clientType,
|
||||||
createApplicationRequestBody(
|
videoId = videoId,
|
||||||
clientType = clientType,
|
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
||||||
videoId = videoId,
|
)
|
||||||
setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -243,15 +189,15 @@ class StreamingDataRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetch(
|
private fun fetch(
|
||||||
videoId: String, playerHeaders: Map<String, String>,
|
videoId: String,
|
||||||
visitorId: String, botGuardPoToken: String
|
requestHeader: Map<String, String>,
|
||||||
): ByteBuffer? {
|
): ByteBuffer? {
|
||||||
lastSpoofedClientType = null
|
lastSpoofedClientFriendlyName = null
|
||||||
|
|
||||||
// Retry with different client if empty response body is received.
|
// Retry with different client if empty response body is received.
|
||||||
for (clientType in CLIENT_ORDER_TO_USE) {
|
for (clientType in CLIENT_ORDER_TO_USE) {
|
||||||
if (clientType.requireAuth &&
|
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" }
|
Logger.printDebug { "Skipped login-required client (incognito mode or not logged in)\nClient: $clientType\nVideo: $videoId" }
|
||||||
continue
|
continue
|
||||||
@ -259,9 +205,7 @@ class StreamingDataRequest private constructor(
|
|||||||
send(
|
send(
|
||||||
clientType,
|
clientType,
|
||||||
videoId,
|
videoId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
visitorId,
|
|
||||||
botGuardPoToken
|
|
||||||
)?.let { connection ->
|
)?.let { connection ->
|
||||||
try {
|
try {
|
||||||
// gzip encoding doesn't response with content length (-1),
|
// gzip encoding doesn't response with content length (-1),
|
||||||
@ -271,14 +215,14 @@ class StreamingDataRequest private constructor(
|
|||||||
} else {
|
} else {
|
||||||
BufferedInputStream(connection.inputStream).use { inputStream ->
|
BufferedInputStream(connection.inputStream).use { inputStream ->
|
||||||
ByteArrayOutputStream().use { stream ->
|
ByteArrayOutputStream().use { stream ->
|
||||||
val buffer = ByteArray(2048)
|
val buffer = ByteArray(4096)
|
||||||
var bytesRead: Int
|
var bytesRead: Int
|
||||||
while ((inputStream.read(buffer)
|
while ((inputStream.read(buffer)
|
||||||
.also { bytesRead = it }) >= 0
|
.also { bytesRead = it }) >= 0
|
||||||
) {
|
) {
|
||||||
stream.write(buffer, 0, bytesRead)
|
stream.write(buffer, 0, bytesRead)
|
||||||
}
|
}
|
||||||
lastSpoofedClientType = clientType
|
lastSpoofedClientFriendlyName = clientType.friendlyName
|
||||||
return ByteBuffer.wrap(stream.toByteArray())
|
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.FALSE;
|
||||||
import static java.lang.Boolean.TRUE;
|
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.ReturnYouTubeUsernamePatch.DisplayFormat;
|
||||||
import app.revanced.extension.shared.patches.WatchHistoryPatch.WatchHistoryType;
|
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;
|
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.
|
* 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 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.
|
* 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_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);
|
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.
|
// 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 EnumSetting<YouTubeAppClient.ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_VR, 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These settings are used by YouTube and YouTube Music.
|
* 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 android.annotation.SuppressLint
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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" }
|
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_VIDEO_DETAILS,
|
GET_VIDEO_DETAILS,
|
||||||
clientType
|
clientType
|
||||||
)
|
)
|
||||||
val requestBody =
|
val requestBody = createWebInnertubeBody(clientType, videoId)
|
||||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package app.revanced.extension.youtube.patches.player.requests
|
package app.revanced.extension.youtube.patches.player.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
import app.revanced.extension.shared.utils.Utils
|
||||||
@ -20,10 +22,10 @@ import java.util.concurrent.TimeoutException
|
|||||||
|
|
||||||
class ActionButtonRequest private constructor(
|
class ActionButtonRequest private constructor(
|
||||||
private val videoId: String,
|
private val videoId: String,
|
||||||
private val playerHeaders: Map<String, String>,
|
private val requestHeader: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
private val future: Future<Array<ActionButton>> = Utils.submitOnBackgroundThread {
|
private val future: Future<Array<ActionButton>> = Utils.submitOnBackgroundThread {
|
||||||
fetch(videoId, playerHeaders)
|
fetch(videoId, requestHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
val array: Array<ActionButton>
|
val array: Array<ActionButton>
|
||||||
@ -52,14 +54,6 @@ class ActionButtonRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -73,11 +67,11 @@ class ActionButtonRequest private constructor(
|
|||||||
})
|
})
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
|
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
if (!cache.containsKey(videoId)) {
|
if (!cache.containsKey(videoId)) {
|
||||||
cache[videoId] = ActionButtonRequest(videoId, playerHeaders)
|
cache[videoId] = ActionButtonRequest(videoId, requestHeader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,43 +87,28 @@ class ActionButtonRequest private constructor(
|
|||||||
Logger.printInfo({ toastMessage }, ex)
|
Logger.printInfo({ toastMessage }, ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val REQUEST_HEADER_KEYS = arrayOf(
|
private fun sendRequest(videoId: String, requestHeader: Map<String, String>): JSONObject? {
|
||||||
"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)
|
Objects.requireNonNull(videoId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
// '/next' request does not require PoToken.
|
// '/next' endpoint does not require PoToken.
|
||||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
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,
|
// 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.
|
// Set the [Authorization] field to property to get the correct action buttons.
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
var value = playerHeaders[key]
|
GET_VIDEO_ACTION_BUTTON,
|
||||||
if (value != null) {
|
clientType,
|
||||||
connection.setRequestProperty(key, value)
|
requestHeader,
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody =
|
val requestBody = createApplicationRequestBody(
|
||||||
PlayerRoutes.createApplicationRequestBody(
|
clientType = clientType,
|
||||||
clientType = clientType,
|
videoId = videoId
|
||||||
videoId = videoId
|
)
|
||||||
)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -214,8 +193,11 @@ class ActionButtonRequest private constructor(
|
|||||||
return emptyArray()
|
return emptyArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Array<ActionButton> {
|
private fun fetch(
|
||||||
val json = sendRequest(videoId, playerHeaders)
|
videoId: String,
|
||||||
|
requestHeader: Map<String, String>
|
||||||
|
): Array<ActionButton> {
|
||||||
|
val json = sendRequest(videoId, requestHeader)
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
return parseResponse(json)
|
return parseResponse(json)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package app.revanced.extension.youtube.patches.utils.requests
|
package app.revanced.extension.youtube.patches.utils.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -20,10 +23,10 @@ import java.util.concurrent.TimeoutException
|
|||||||
|
|
||||||
class CreatePlaylistRequest private constructor(
|
class CreatePlaylistRequest private constructor(
|
||||||
private val videoId: String,
|
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 {
|
private val future: Future<Pair<String, String>> = Utils.submitOnBackgroundThread {
|
||||||
fetch(videoId, playerHeaders)
|
fetch(videoId, requestHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
val playlistId: Pair<String, String>?
|
val playlistId: Pair<String, String>?
|
||||||
@ -52,14 +55,6 @@ class CreatePlaylistRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -80,11 +75,11 @@ class CreatePlaylistRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
|
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
if (!cache.containsKey(videoId)) {
|
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)
|
Logger.printInfo({ toastMessage }, ex)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val REQUEST_HEADER_KEYS = arrayOf(
|
private fun sendCreatePlaylistRequest(
|
||||||
"Authorization", // Available only to logged-in users.
|
videoId: String,
|
||||||
"X-GOOG-API-FORMAT-VERSION",
|
requestHeader: Map<String, String>
|
||||||
"X-Goog-Visitor-Id"
|
): JSONObject? {
|
||||||
)
|
|
||||||
|
|
||||||
private fun sendCreatePlaylistRequest(videoId: String, playerHeaders: Map<String, String>): JSONObject? {
|
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
// 'playlist/create' request does not require PoToken.
|
// 'playlist/create' endpoint does not require PoToken.
|
||||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching create playlist request for: $videoId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching create playlist request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.CREATE_PLAYLIST,
|
CREATE_PLAYLIST,
|
||||||
clientType
|
clientType,
|
||||||
|
requestHeader,
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = createPlaylistRequestBody(videoId = videoId)
|
||||||
var value = playerHeaders[key]
|
|
||||||
if (value != null) {
|
|
||||||
connection.setRequestProperty(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody =
|
|
||||||
PlayerRoutes.createPlaylistRequestBody(
|
|
||||||
videoId = videoId
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -159,36 +140,31 @@ class CreatePlaylistRequest private constructor(
|
|||||||
return null
|
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)
|
Objects.requireNonNull(playlistId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
// 'playlist/create' request does not require PoToken.
|
// 'playlist/create' endpoint does not require PoToken.
|
||||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching set video id request for: $playlistId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching set video id request for: $playlistId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_SET_VIDEO_ID,
|
GET_SET_VIDEO_ID,
|
||||||
clientType
|
clientType,
|
||||||
|
requestHeader
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = createApplicationRequestBody(
|
||||||
var value = playerHeaders[key]
|
clientType = clientType,
|
||||||
if (value != null) {
|
videoId = videoId,
|
||||||
connection.setRequestProperty(key, value)
|
playlistId = playlistId
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody =
|
|
||||||
PlayerRoutes.createApplicationRequestBody(
|
|
||||||
clientType = clientType,
|
|
||||||
videoId = videoId,
|
|
||||||
playlistId = playlistId
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -240,8 +216,8 @@ class CreatePlaylistRequest private constructor(
|
|||||||
|
|
||||||
if (secondaryContentsJsonObject is JSONObject) {
|
if (secondaryContentsJsonObject is JSONObject) {
|
||||||
return secondaryContentsJsonObject
|
return secondaryContentsJsonObject
|
||||||
.getJSONObject("playlistPanelVideoRenderer")
|
.getJSONObject("playlistPanelVideoRenderer")
|
||||||
.getString("playlistSetVideoId")
|
.getString("playlistSetVideoId")
|
||||||
}
|
}
|
||||||
} catch (e: JSONException) {
|
} catch (e: JSONException) {
|
||||||
val jsonForMessage = json.toString()
|
val jsonForMessage = json.toString()
|
||||||
@ -254,12 +230,15 @@ class CreatePlaylistRequest private constructor(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Pair<String, String>? {
|
private fun fetch(
|
||||||
val createPlaylistJson = sendCreatePlaylistRequest(videoId, playerHeaders)
|
videoId: String,
|
||||||
|
requestHeader: Map<String, String>
|
||||||
|
): Pair<String, String>? {
|
||||||
|
val createPlaylistJson = sendCreatePlaylistRequest(videoId, requestHeader)
|
||||||
if (createPlaylistJson != null) {
|
if (createPlaylistJson != null) {
|
||||||
val playlistId = parseCreatePlaylistResponse(createPlaylistJson)
|
val playlistId = parseCreatePlaylistResponse(createPlaylistJson)
|
||||||
if (playlistId != null) {
|
if (playlistId != null) {
|
||||||
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, playerHeaders)
|
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, requestHeader)
|
||||||
if (setVideoIdJson != null) {
|
if (setVideoIdJson != null) {
|
||||||
val setVideoId = parseSetVideoIdResponse(setVideoIdJson)
|
val setVideoId = parseSetVideoIdResponse(setVideoIdJson)
|
||||||
if (setVideoId != null) {
|
if (setVideoId != null) {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package app.revanced.extension.youtube.patches.utils.requests
|
package app.revanced.extension.youtube.patches.utils.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -20,12 +21,12 @@ import java.util.concurrent.TimeoutException
|
|||||||
|
|
||||||
class DeletePlaylistRequest private constructor(
|
class DeletePlaylistRequest private constructor(
|
||||||
private val playlistId: String,
|
private val playlistId: String,
|
||||||
private val playerHeaders: Map<String, String>,
|
private val requestHeader: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||||
fetch(
|
fetch(
|
||||||
playlistId,
|
playlistId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,14 +56,6 @@ class DeletePlaylistRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -85,14 +78,14 @@ class DeletePlaylistRequest private constructor(
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fetchRequestIfNeeded(
|
fun fetchRequestIfNeeded(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
) {
|
) {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
if (!cache.containsKey(playlistId)) {
|
if (!cache.containsKey(playlistId)) {
|
||||||
cache[playlistId] = DeletePlaylistRequest(
|
cache[playlistId] = DeletePlaylistRequest(
|
||||||
playlistId,
|
playlistId,
|
||||||
playerHeaders
|
requestHeader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,40 +102,26 @@ class DeletePlaylistRequest private constructor(
|
|||||||
Logger.printInfo({ toastMessage }, ex)
|
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(
|
private fun sendRequest(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): JSONObject? {
|
): JSONObject? {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
// 'playlist/delete' request does not require PoToken.
|
// 'playlist/delete' endpoint does not require PoToken.
|
||||||
val clientType = YouTubeAppClient.ClientType.ANDROID
|
val clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching delete playlist request, playlistId: $playlistId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching delete playlist request, playlistId: $playlistId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.DELETE_PLAYLIST,
|
DELETE_PLAYLIST,
|
||||||
clientType,
|
clientType,
|
||||||
|
requestHeader
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = deletePlaylistRequestBody(playlistId)
|
||||||
var value = playerHeaders[key]
|
|
||||||
if (value != null) {
|
|
||||||
connection.setRequestProperty(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody = PlayerRoutes.deletePlaylistRequestBody(playlistId)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -184,9 +163,9 @@ class DeletePlaylistRequest private constructor(
|
|||||||
|
|
||||||
private fun fetch(
|
private fun fetch(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): Boolean? {
|
): Boolean? {
|
||||||
val json = sendRequest(playlistId, playerHeaders)
|
val json = sendRequest(playlistId, requestHeader)
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
return parseResponse(json)
|
return parseResponse(json)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package app.revanced.extension.youtube.patches.utils.requests
|
package app.revanced.extension.youtube.patches.utils.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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.apache.commons.lang3.StringUtils
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -23,14 +24,14 @@ class EditPlaylistRequest private constructor(
|
|||||||
private val videoId: String,
|
private val videoId: String,
|
||||||
private val playlistId: String,
|
private val playlistId: String,
|
||||||
private val setVideoId: String?,
|
private val setVideoId: String?,
|
||||||
private val playerHeaders: Map<String, String>,
|
private val requestHeader: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||||
fetch(
|
fetch(
|
||||||
videoId,
|
videoId,
|
||||||
playlistId,
|
playlistId,
|
||||||
setVideoId,
|
setVideoId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,14 +61,6 @@ class EditPlaylistRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -99,7 +92,7 @@ class EditPlaylistRequest private constructor(
|
|||||||
videoId: String,
|
videoId: String,
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
setVideoId: String?,
|
setVideoId: String?,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
) {
|
) {
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
@ -108,7 +101,7 @@ class EditPlaylistRequest private constructor(
|
|||||||
videoId,
|
videoId,
|
||||||
playlistId,
|
playlistId,
|
||||||
setVideoId,
|
setVideoId,
|
||||||
playerHeaders
|
requestHeader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,47 +118,32 @@ class EditPlaylistRequest private constructor(
|
|||||||
Logger.printInfo({ toastMessage }, ex)
|
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(
|
private fun sendRequest(
|
||||||
videoId: String,
|
videoId: String,
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
setVideoId: String?,
|
setVideoId: String?,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): JSONObject? {
|
): JSONObject? {
|
||||||
Objects.requireNonNull(videoId)
|
Objects.requireNonNull(videoId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
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 clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching edit playlist request, videoId: $videoId, playlistId: $playlistId, setVideoId: $setVideoId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching edit playlist request, videoId: $videoId, playlistId: $playlistId, setVideoId: $setVideoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.EDIT_PLAYLIST,
|
EDIT_PLAYLIST,
|
||||||
clientType
|
clientType,
|
||||||
|
requestHeader
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = editPlaylistRequestBody(
|
||||||
var value = playerHeaders[key]
|
videoId = videoId,
|
||||||
if (value != null) {
|
playlistId = playlistId,
|
||||||
connection.setRequestProperty(key, value)
|
setVideoId = setVideoId
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody =
|
|
||||||
PlayerRoutes.editPlaylistRequestBody(
|
|
||||||
videoId = videoId,
|
|
||||||
playlistId = playlistId,
|
|
||||||
setVideoId = setVideoId,
|
|
||||||
)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -197,7 +175,8 @@ class EditPlaylistRequest private constructor(
|
|||||||
if (remove) {
|
if (remove) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
val playlistEditResultsJSONObject = json.getJSONArray("playlistEditResults").get(0)
|
val playlistEditResultsJSONObject =
|
||||||
|
json.getJSONArray("playlistEditResults").get(0)
|
||||||
|
|
||||||
if (playlistEditResultsJSONObject is JSONObject) {
|
if (playlistEditResultsJSONObject is JSONObject) {
|
||||||
return playlistEditResultsJSONObject
|
return playlistEditResultsJSONObject
|
||||||
@ -220,9 +199,9 @@ class EditPlaylistRequest private constructor(
|
|||||||
videoId: String,
|
videoId: String,
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
setVideoId: String?,
|
setVideoId: String?,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): String? {
|
): String? {
|
||||||
val json = sendRequest(videoId, playlistId, setVideoId, playerHeaders)
|
val json = sendRequest(videoId, playlistId, setVideoId, requestHeader)
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
|
return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package app.revanced.extension.youtube.patches.utils.requests
|
package app.revanced.extension.youtube.patches.utils.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -20,12 +21,12 @@ import java.util.concurrent.TimeoutException
|
|||||||
|
|
||||||
class GetPlaylistsRequest private constructor(
|
class GetPlaylistsRequest private constructor(
|
||||||
private val playlistId: String,
|
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 {
|
private val future: Future<Array<Pair<String, String>>> = Utils.submitOnBackgroundThread {
|
||||||
fetch(
|
fetch(
|
||||||
playlistId,
|
playlistId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,14 +56,6 @@ class GetPlaylistsRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -85,14 +78,14 @@ class GetPlaylistsRequest private constructor(
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fetchRequestIfNeeded(
|
fun fetchRequestIfNeeded(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
) {
|
) {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
if (!cache.containsKey(playlistId)) {
|
if (!cache.containsKey(playlistId)) {
|
||||||
cache[playlistId] = GetPlaylistsRequest(
|
cache[playlistId] = GetPlaylistsRequest(
|
||||||
playlistId,
|
playlistId,
|
||||||
playerHeaders
|
requestHeader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,40 +102,26 @@ class GetPlaylistsRequest private constructor(
|
|||||||
Logger.printInfo({ toastMessage }, ex)
|
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(
|
private fun sendRequest(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): JSONObject? {
|
): JSONObject? {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
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 clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching get playlists request, playlistId: $playlistId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching get playlists request, playlistId: $playlistId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_PLAYLISTS,
|
GET_PLAYLISTS,
|
||||||
clientType
|
clientType,
|
||||||
|
requestHeader
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = getPlaylistsRequestBody(playlistId)
|
||||||
var value = playerHeaders[key]
|
|
||||||
if (value != null) {
|
|
||||||
connection.setRequestProperty(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody = PlayerRoutes.getPlaylistsRequestBody(playlistId)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -223,9 +202,9 @@ class GetPlaylistsRequest private constructor(
|
|||||||
|
|
||||||
private fun fetch(
|
private fun fetch(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): Array<Pair<String, String>>? {
|
): Array<Pair<String, String>>? {
|
||||||
val json = sendRequest(playlistId, playerHeaders)
|
val json = sendRequest(playlistId, requestHeader)
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
return parseResponse(json)
|
return parseResponse(json)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package app.revanced.extension.youtube.patches.utils.requests
|
package app.revanced.extension.youtube.patches.utils.requests
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -21,13 +22,13 @@ import java.util.concurrent.TimeoutException
|
|||||||
class SavePlaylistRequest private constructor(
|
class SavePlaylistRequest private constructor(
|
||||||
private val playlistId: String,
|
private val playlistId: String,
|
||||||
private val libraryId: String,
|
private val libraryId: String,
|
||||||
private val playerHeaders: Map<String, String>,
|
private val requestHeader: Map<String, String>,
|
||||||
) {
|
) {
|
||||||
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
|
||||||
fetch(
|
fetch(
|
||||||
playlistId,
|
playlistId,
|
||||||
libraryId,
|
libraryId,
|
||||||
playerHeaders,
|
requestHeader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,14 +58,6 @@ class SavePlaylistRequest private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
|
||||||
|
|
||||||
@GuardedBy("itself")
|
@GuardedBy("itself")
|
||||||
@ -88,14 +81,14 @@ class SavePlaylistRequest private constructor(
|
|||||||
fun fetchRequestIfNeeded(
|
fun fetchRequestIfNeeded(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
libraryId: String,
|
libraryId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
) {
|
) {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
synchronized(cache) {
|
synchronized(cache) {
|
||||||
cache[libraryId] = SavePlaylistRequest(
|
cache[libraryId] = SavePlaylistRequest(
|
||||||
playlistId,
|
playlistId,
|
||||||
libraryId,
|
libraryId,
|
||||||
playerHeaders
|
requestHeader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,43 +104,28 @@ class SavePlaylistRequest private constructor(
|
|||||||
Logger.printInfo({ toastMessage }, ex)
|
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(
|
private fun sendRequest(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
libraryId: String,
|
libraryId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): JSONObject? {
|
): JSONObject? {
|
||||||
Objects.requireNonNull(playlistId)
|
Objects.requireNonNull(playlistId)
|
||||||
Objects.requireNonNull(libraryId)
|
Objects.requireNonNull(libraryId)
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
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 clientType = YouTubeAppClient.ClientType.ANDROID
|
||||||
val clientTypeName = clientType.name
|
val clientTypeName = clientType.name
|
||||||
Logger.printDebug { "Fetching edit playlist request, playlistId: $playlistId, libraryId: $libraryId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching edit playlist request, playlistId: $playlistId, libraryId: $libraryId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.EDIT_PLAYLIST,
|
EDIT_PLAYLIST,
|
||||||
clientType
|
clientType,
|
||||||
|
requestHeader
|
||||||
)
|
)
|
||||||
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
|
|
||||||
|
|
||||||
for (key in REQUEST_HEADER_KEYS) {
|
val requestBody = savePlaylistRequestBody(libraryId, playlistId)
|
||||||
var value = playerHeaders[key]
|
|
||||||
if (value != null) {
|
|
||||||
connection.setRequestProperty(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody =
|
|
||||||
PlayerRoutes.savePlaylistRequestBody(libraryId, playlistId)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
@ -190,9 +168,9 @@ class SavePlaylistRequest private constructor(
|
|||||||
private fun fetch(
|
private fun fetch(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
libraryId: String,
|
libraryId: String,
|
||||||
playerHeaders: Map<String, String>
|
requestHeader: Map<String, String>
|
||||||
): Boolean? {
|
): Boolean? {
|
||||||
val json = sendRequest(playlistId, libraryId,playerHeaders)
|
val json = sendRequest(playlistId, libraryId, requestHeader)
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
return parseResponse(json)
|
return parseResponse(json)
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,13 @@ package app.revanced.extension.youtube.patches.video.requests
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.annotation.GuardedBy
|
import androidx.annotation.GuardedBy
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeAppClient
|
import app.revanced.extension.shared.innertube.client.YouTubeAppClient
|
||||||
import app.revanced.extension.shared.patches.client.YouTubeWebClient
|
import app.revanced.extension.shared.innertube.client.YouTubeWebClient
|
||||||
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
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.requests.Requester
|
||||||
import app.revanced.extension.shared.utils.Logger
|
import app.revanced.extension.shared.utils.Logger
|
||||||
import app.revanced.extension.shared.utils.Utils
|
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" }
|
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_PLAYLIST_PAGE,
|
GET_PLAYLIST_PAGE,
|
||||||
clientType
|
clientType
|
||||||
)
|
)
|
||||||
val requestBody =
|
val requestBody =
|
||||||
PlayerRoutes.createApplicationRequestBody(
|
createApplicationRequestBody(
|
||||||
clientType = clientType,
|
clientType = clientType,
|
||||||
videoId = videoId,
|
videoId = videoId,
|
||||||
playlistId = "RD$videoId"
|
playlistId = "RD$videoId"
|
||||||
@ -168,12 +172,11 @@ class MusicRequest private constructor(
|
|||||||
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
val connection = getInnerTubeResponseConnectionFromRoute(
|
||||||
PlayerRoutes.GET_CATEGORY,
|
GET_CATEGORY,
|
||||||
clientType
|
clientType
|
||||||
)
|
)
|
||||||
val requestBody =
|
val requestBody = createWebInnertubeBody(clientType, videoId)
|
||||||
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
|
||||||
|
|
||||||
connection.setFixedLengthStreamingMode(requestBody.size)
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
connection.outputStream.write(requestBody)
|
connection.outputStream.write(requestBody)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user