diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ddecc639..6402a548 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 = 22 - versionName = "0.2.5" + versionCode = 23 + versionName = "0.2.6" vectorDrawables.useSupportLibrary = true ksp { diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt new file mode 100644 index 00000000..e04c67fb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/23.txt @@ -0,0 +1 @@ +- Hot fix YouTube API issue \ No newline at end of file diff --git a/fastlane/metadata/android/vi-VN/changelogs/23.txt b/fastlane/metadata/android/vi-VN/changelogs/23.txt new file mode 100644 index 00000000..654992c4 --- /dev/null +++ b/fastlane/metadata/android/vi-VN/changelogs/23.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/Ytmusic.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt index 77ebc1d0..f021ba39 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/Ytmusic.kt @@ -101,7 +101,7 @@ class Ytmusic { gl = Locale.getDefault().country, hl = Locale.getDefault().toLanguageTag(), ) - var visitorData: String = "CgtrcUxtWU5xWDJlVSivkpa7BjIKCgJWThIEGgAgHg%3D%3D" + var visitorData: String = "CgtsZG1ySnZiQWtSbyiMjuGSBg%3D%3D" var cookie: String? = null set(value) { field = value @@ -346,16 +346,9 @@ class Ytmusic { append("Cookie", cookie) if ("SAPISID" !in cookieMap || "__Secure-3PAPISID" !in cookieMap) return@let val currentTime = System.currentTimeMillis() / 1000 - if (client != WEB_REMIX) { - val sapisidHash = sha1("$currentTime ${cookieMap["SAPISID"]} https://music.youtube.com") - append("Authorization", "SAPISIDHASH ${currentTime}_$sapisidHash") - } else { - val sapisidHash = sha1("$currentTime ${cookieMap["__Secure-3PAPISID"]} https://music.youtube.com") - append( - "Authorization", - "SAPISIDHASH ${currentTime}_$sapisidHash", - ) - } + val sapisidCookie = cookieMap["SAPISID"] ?: cookieMap["__Secure-3PAPISID"] + val sapisidHash = sha1("$currentTime $sapisidCookie https://music.youtube.com") + append("Authorization", "SAPISIDHASH ${currentTime}_$sapisidHash") } } } @@ -388,18 +381,30 @@ class Ytmusic { } suspend fun noLogInPlayer(videoId: String) = - httpClient.post("player") { + httpClient.post("https://www.youtube.com/youtubei/v1/player") { accept(ContentType.Application.Json) contentType(ContentType.Application.Json) - header("Host", "music.youtube.com") + 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, visitorData), + context = IOS.toContext(locale, null), playlistId = null, cpn = null, videoId = videoId, + playbackContext = PlayerBody.PlaybackContext(), ), ) + parameter("prettyPrint", false) } suspend fun player( diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/Context.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/Context.kt index 108909a2..3b39d007 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/Context.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/Context.kt @@ -11,10 +11,16 @@ data class Context( data class Client( val clientName: String, val clientVersion: String, + val deviceMake: String? = null, + val deviceModel: String? = null, + val userAgent: String, val gl: String, val hl: String, val visitorData: String?, + val osName: String? = null, val osVersion: String?, + val timeZone: String? = null, + val utcOffsetMinutes: Int? = null, ) @Serializable diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/YouTubeClient.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/YouTubeClient.kt index a863c1ac..87eff241 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/YouTubeClient.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/YouTubeClient.kt @@ -10,6 +10,11 @@ data class YouTubeClient( val userAgent: String, val osVersion: String? = null, val referer: String? = null, + val deviceMake: String? = null, + val deviceModel: String? = null, + val osName: String? = null, + val timeZone: String? = null, + val utcOffsetMinutes: Int? = null, ) { fun toContext( locale: YouTubeLocale, @@ -23,6 +28,12 @@ data class YouTubeClient( gl = locale.gl, hl = locale.hl, visitorData = visitorData, + userAgent = userAgent, + deviceMake = deviceMake, + deviceModel = deviceModel, + osName = osName, + timeZone = timeZone, + utcOffsetMinutes = utcOffsetMinutes, ), ) @@ -33,7 +44,7 @@ data class YouTubeClient( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" private const val USER_AGENT_ANDROID = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Mobile Safari/537.36" - private const val USER_AGENT_IOS = "com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X;)" + private const val USER_AGENT_IOS = "com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X;)" val ANDROID_MUSIC = YouTubeClient( @@ -79,10 +90,15 @@ data class YouTubeClient( val IOS = YouTubeClient( clientName = "IOS", - clientVersion = "19.29.1", - api_key = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc", + clientVersion = "19.45.4", + deviceMake = "Apple", + deviceModel = "iPhone16,2", userAgent = USER_AGENT_IOS, + api_key = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc", + osName = "iPhone", osVersion = "17.5.1.21F90", + timeZone = "UTC", + utcOffsetMinutes = 0, ) } } \ No newline at end of file diff --git a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/body/PlayerBody.kt b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/body/PlayerBody.kt index d88d238c..c546a4d0 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/body/PlayerBody.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/models/body/PlayerBody.kt @@ -10,4 +10,17 @@ data class PlayerBody( val playlistId: String?, val cpn: String?, val contentCheckOk: Boolean = true, -) \ No newline at end of file + val racyCheckOk: Boolean = true, + val playbackContext: PlaybackContext? = null, +) { + @Serializable + data class PlaybackContext( + val contentPlaybackContext: ContentPlaybackContext = ContentPlaybackContext(), + ) { + @Serializable + data class ContentPlaybackContext( + val html5Preference: String = "HTML5_PREF_WANTS", + val signatureTimestamp: Int = 20073, + ) + } +} \ 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 4c9e14a4..34b72f39 100644 --- a/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt +++ b/kotlinYtmusicScraper/src/main/java/com/maxrave/kotlinytmusicscraper/test/main.kt @@ -25,7 +25,8 @@ fun main() { fun testPlayer() { runBlocking { Ytmusic() - .player(YouTubeClient.IOS, videoId = "UFkm74XwStQ", null, null) + .apply {} + .noLogInPlayer("Fwsl_XS4sYQ") .body() .also { println(it.playabilityStatus.status)