diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6402a548..f075b4d2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = "com.maxrave.simpmusic" minSdk = 26 targetSdk = 35 - versionCode = 23 - versionName = "0.2.6" + versionCode = 24 + versionName = "0.2.7" vectorDrawables.useSupportLibrary = true ksp { diff --git a/fastlane/metadata/android/en-US/changelogs/24.txt b/fastlane/metadata/android/en-US/changelogs/24.txt new file mode 100644 index 00000000..e04c67fb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/24.txt @@ -0,0 +1 @@ +- Hot fix YouTube API issue \ No newline at end of file diff --git a/fastlane/metadata/android/vi-VN/changelogs/24.txt b/fastlane/metadata/android/vi-VN/changelogs/24.txt new file mode 100644 index 00000000..654992c4 --- /dev/null +++ b/fastlane/metadata/android/vi-VN/changelogs/24.txt @@ -0,0 +1 @@ +- Sửa lỗi API YouTube \ No newline at end of file diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt index 874cd67d..aa2fdd98 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/YouTube.kt @@ -53,6 +53,7 @@ import com.maxrave.kotlinytmusicscraper.models.response.spotify.search.SpotifySe import com.maxrave.kotlinytmusicscraper.models.response.toLikeStatus import com.maxrave.kotlinytmusicscraper.models.simpmusic.GithubResponse import com.maxrave.kotlinytmusicscraper.models.sponsorblock.SkipSegments +import com.maxrave.kotlinytmusicscraper.models.youtube.GhostResponse import com.maxrave.kotlinytmusicscraper.models.youtube.Transcript import com.maxrave.kotlinytmusicscraper.models.youtube.YouTubeInitialPage import com.maxrave.kotlinytmusicscraper.pages.AlbumPage @@ -1280,12 +1281,52 @@ class YouTube { }.joinToString("") val playerResponse = try { - Log.w("Player Response", "Try Android") - ytMusic.player(if (cookie != null) ANDROID_MUSIC else IOS, videoId, playlistId, cpn).body() +// Log.w("Player Response", "Try Android") + ytMusic.player(if (!cookie.isNullOrEmpty()) ANDROID_MUSIC else IOS, videoId, playlistId, cpn).body().also { + if (it.playabilityStatus.status != "OK") throw Exception(it.playabilityStatus.status) + } } catch (e: Exception) { println("Player Response Error $e") - Log.w("Player Response", "Try IOS") - ytMusic.noLogInPlayer(videoId).body() +// Log.w("Player Response", "Try IOS") + val ghostRequest = ytMusic.ghostRequest(videoId) + val cookie = + "PREF=hl=en&tz=UTC; SOCS=CAI; ${ghostRequest.headers + .getAll("set-cookie") + ?.map { + it.split(";").first() + }?.filter { + it.lastOrNull() != '=' + }?.joinToString("; ")}" + var response = "" + val ksoupHtmlParser = + KsoupHtmlParser( + object : KsoupHtmlHandler { + override fun onText(text: String) { + super.onText(text) + if (text.contains("var ytInitialPlayerResponse")) { + print("Text $text") + val temp = text.replace("var ytInitialPlayerResponse = ", "").split(";var").firstOrNull() + println("Scrape Temp $temp") + temp?.let { + response = it.trimIndent() + } + } + } + }, + ) + ksoupHtmlParser.write(ghostRequest.bodyAsText()) + ksoupHtmlParser.end() + val json = Json { ignoreUnknownKeys = true } + val jsonData = json.decodeFromString(response) + val visitorData = + jsonData.responseContext.serviceTrackingParams + ?.find { it.service == "GFEEDBACK" } + ?.params + ?.find { it.key == "visitor_data" } + ?.value + println("Visitor Data $visitorData") + println("Cookie $cookie") + ytMusic.noLogInPlayer(videoId, cookie, visitorData).body() } println("Player Response $playerResponse") println("Thumbnails " + playerResponse.videoDetails?.thumbnail) diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt index f021ba39..c4394d4e 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt @@ -380,32 +380,50 @@ class Ytmusic { contentType(ContentType.Application.Json) } - suspend fun noLogInPlayer(videoId: String) = - httpClient.post("https://www.youtube.com/youtubei/v1/player") { - accept(ContentType.Application.Json) - contentType(ContentType.Application.Json) - header("Host", "www.youtube.com") - header("Origin", "https://www.youtube.com") - header("Sec-Fetch-Mode", "navigate") - header(HttpHeaders.UserAgent, IOS.userAgent) - header( - "Set-Cookie", - "PREF=hl=en&tz=UTC; SOCS=CAI; GPS=1; YSC=ftafXBDpV4c; VISITOR_INFO1_LIVE=mk4zk5sq6VY; VISITOR_PRIVACY_METADATA=CgJWThIEGgAgRQ%3D%3D", - ) - header("X-Goog-Visitor-Id", "CgttazR6azVzcTZWWSiMp6u7BjIKCgJWThIEGgAgRQ%3D%3D") - header("X-YouTube-Client-Name", IOS.clientName) - header("X-YouTube-Client-Version", IOS.clientVersion) - setBody( - PlayerBody( - context = IOS.toContext(locale, null), - playlistId = null, - cpn = null, - videoId = videoId, - playbackContext = PlayerBody.PlaybackContext(), - ), - ) - parameter("prettyPrint", false) - } + suspend fun ghostRequest(videoId: String) = + httpClient + .get("https://www.youtube.com/watch?v=$videoId&bpctr=9999999999&has_verified=1") { + headers { + header("Connection", "close") + header("Host", "www.youtube.com") + header("Cookie", "PREF=hl=en&tz=UTC; SOCS=CAI") + header("Sec-Fetch-Mode", "navigate") + header( + "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36", + ) + } + } + + suspend fun noLogInPlayer( + videoId: String, + cookie: String, + visitorData: String?, + ) = httpClient.post("https://www.youtube.com/youtubei/v1/player") { + accept(ContentType.Application.Json) + contentType(ContentType.Application.Json) + header("Host", "www.youtube.com") + header("Origin", "https://www.youtube.com") + header("Sec-Fetch-Mode", "navigate") + header(HttpHeaders.UserAgent, IOS.userAgent) + header( + "Set-Cookie", + cookie, + ) + header("X-Goog-Visitor-Id", visitorData ?: this@Ytmusic.visitorData) + header("X-YouTube-Client-Name", IOS.clientName) + header("X-YouTube-Client-Version", IOS.clientVersion) + setBody( + PlayerBody( + context = IOS.toContext(locale, null), + playlistId = null, + cpn = null, + videoId = videoId, + playbackContext = PlayerBody.PlaybackContext(), + ), + ) + parameter("prettyPrint", false) + } suspend fun player( client: YouTubeClient, diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/ResponseContext.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/ResponseContext.kt index 8b88d566..2278edcc 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/ResponseContext.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/ResponseContext.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.Serializable @Serializable data class ResponseContext( - val visitorData: String?, + val visitorData: String? = null, val serviceTrackingParams: List?, ) { @Serializable diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/youtube/YouTubeInitialPage.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/youtube/YouTubeInitialPage.kt index e800788d..406cb90b 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/youtube/YouTubeInitialPage.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/youtube/YouTubeInitialPage.kt @@ -1,5 +1,6 @@ package com.maxrave.kotlinytmusicscraper.models.youtube +import com.maxrave.kotlinytmusicscraper.models.ResponseContext import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -37,4 +38,9 @@ data class YouTubeInitialPage( } } } -} \ No newline at end of file +} + +@Serializable +data class GhostResponse( + val responseContext: ResponseContext, +) \ No newline at end of file diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt index 34b72f39..c61d8e20 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt @@ -1,6 +1,7 @@ package com.maxrave.kotlinytmusicscraper.test import com.google.gson.annotations.SerializedName +import com.maxrave.kotlinytmusicscraper.YouTube import com.maxrave.kotlinytmusicscraper.Ytmusic import com.maxrave.kotlinytmusicscraper.models.GridRenderer import com.maxrave.kotlinytmusicscraper.models.MusicResponsiveListItemRenderer @@ -10,11 +11,8 @@ import com.maxrave.kotlinytmusicscraper.models.SectionListRenderer import com.maxrave.kotlinytmusicscraper.models.Thumbnail import com.maxrave.kotlinytmusicscraper.models.YouTubeClient import com.maxrave.kotlinytmusicscraper.models.YouTubeLocale -import com.maxrave.kotlinytmusicscraper.models.response.PlayerResponse -import io.ktor.client.call.body import io.ktor.client.statement.bodyAsText import kotlinx.coroutines.runBlocking -import java.util.Collections.addAll fun main() { runBlocking { @@ -24,20 +22,14 @@ fun main() { fun testPlayer() { runBlocking { - Ytmusic() + YouTube() .apply {} - .noLogInPlayer("Fwsl_XS4sYQ") - .body() - .also { - println(it.playabilityStatus.status) - val listFormat = (it.streamingData?.formats?.toMutableList() ?: mutableListOf()) - listFormat.addAll(it.streamingData?.adaptiveFormats ?: mutableListOf()) - listFormat - .map { - it.itag to it.url - }.forEach { data -> - println(data) - } + .player( + "Fwsl_XS4sYQ", + ).onSuccess { + println(it) + }.onFailure { + it.printStackTrace() } } }