Skip to content

Commit

Permalink
fix: play song without loggin
Browse files Browse the repository at this point in the history
reversed cna function for n and signature function, transpiled into kotlin
  • Loading branch information
Malopieds committed Oct 28, 2024
1 parent ceaa82d commit 6b2318a
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 12 deletions.
14 changes: 12 additions & 2 deletions app/src/main/java/com/malopieds/innertune/playback/MusicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -818,9 +818,19 @@ class MusicService :
)
}
scope.launch(Dispatchers.IO) { recoverSong(mediaId, playerResponse) }
val url =
run {
runBlocking(Dispatchers.IO) {
format.findUrl()
}
}

songUrlCache[mediaId] = format.url!! to playerResponse.streamingData!!.expiresInSeconds * 1000L
dataSpec.withUri(format.url!!.toUri()).subrange(dataSpec.uriPositionOffset, CHUNK_LENGTH)
songUrlCache[mediaId] = url to (
playerResponse.streamingData?.expiresInSeconds?.times(
1000L,
) ?: 0
)
dataSpec.withUri(url.toUri()).subrange(dataSpec.uriPositionOffset, CHUNK_LENGTH)
}
}

Expand Down
17 changes: 17 additions & 0 deletions innertube/src/main/java/com/malopieds/innertube/InnerTube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import com.malopieds.innertube.models.body.GetTranscriptBody
import com.malopieds.innertube.models.body.NextBody
import com.malopieds.innertube.models.body.PlayerBody
import com.malopieds.innertube.models.body.SearchBody
import com.malopieds.innertube.utils.nSigDecode
import com.malopieds.innertube.utils.parseCookieString
import com.malopieds.innertube.utils.sha1
import com.malopieds.innertube.utils.sigDecode
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.compression.ContentEncoding
Expand All @@ -26,7 +28,9 @@ import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.URLBuilder
import io.ktor.http.contentType
import io.ktor.http.parseQueryString
import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.encodeBase64
Expand Down Expand Up @@ -269,4 +273,17 @@ class InnerTube {
ytClient(client, setLogin = true)
setBody(AccountMenuBody(client.toContext(locale, visitorData)))
}

fun decodeCipher(cipher: String): String? {
val params = parseQueryString(cipher)
val signature = params["s"] ?: return null
val signatureParam = params["sp"] ?: return null
val url = params["url"]?.let { URLBuilder(it) } ?: return null
val n = url.parameters["n"]
url.parameters["n"] = nSigDecode(n.toString())
url.parameters[signatureParam] = sigDecode(signature)
url.parameters["c"] = "ANDROID_MUSIC"
println("url = $url")
return url.toString()
}
}
23 changes: 17 additions & 6 deletions innertube/src/main/java/com/malopieds/innertube/YouTube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.malopieds.innertube.models.SongItem
import com.malopieds.innertube.models.WatchEndpoint
import com.malopieds.innertube.models.WatchEndpoint.WatchEndpointMusicSupportedConfigs.WatchEndpointMusicConfig.Companion.MUSIC_VIDEO_TYPE_ATV
import com.malopieds.innertube.models.YouTubeClient.Companion.ANDROID_MUSIC
import com.malopieds.innertube.models.YouTubeClient.Companion.TVHTML5
import com.malopieds.innertube.models.YouTubeClient.Companion.IOS
import com.malopieds.innertube.models.YouTubeClient.Companion.WEB
import com.malopieds.innertube.models.YouTubeClient.Companion.WEB_REMIX
import com.malopieds.innertube.models.YouTubeLocale
Expand Down Expand Up @@ -877,19 +877,30 @@ object YouTube {
}
}

private val PlayerResponse.isValid
get() =
playabilityStatus.status == "OK" &&
streamingData?.adaptiveFormats?.any { it.url != null || it.signatureCipher != null } == true

suspend fun player(
videoId: String,
playlistId: String? = null,
): Result<PlayerResponse> =
runCatching {
val playerResponse = innerTube.player(ANDROID_MUSIC, videoId, playlistId).body<PlayerResponse>()
if (playerResponse.playabilityStatus.status == "OK") {
return@runCatching playerResponse
val safePlayerResponse = innerTube.player(WEB_REMIX, videoId, playlistId).body<PlayerResponse>()
if (safePlayerResponse.isValid) {
return@runCatching safePlayerResponse
}
val safePlayerResponse = innerTube.player(TVHTML5, videoId, playlistId).body<PlayerResponse>()
if (safePlayerResponse.playabilityStatus.status != "OK") {
val playerResponse =
innerTube.player(IOS, videoId, playlistId).body<PlayerResponse>()
if (playerResponse.isValid) {
return@runCatching playerResponse
}
val androidPlayerResponse =
innerTube.player(ANDROID_MUSIC, videoId, playlistId).body<PlayerResponse>()
if (androidPlayerResponse.playabilityStatus.status == "OK") {
return@runCatching androidPlayerResponse
}
val audioStreams = innerTube.pipedStreams(videoId).body<PipedResponse>().audioStreams
safePlayerResponse.copy(
streamingData =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data class Context(
data class Client(
val clientName: String,
val clientVersion: String,
val osVersion: String?,
val gl: String,
val hl: String,
val visitorData: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
data class YouTubeClient(
val clientName: String,
val clientVersion: String,
val osVersion: String? = null,
val api_key: String,
val userAgent: String,
val referer: String? = null,
Expand All @@ -18,6 +19,7 @@ data class YouTubeClient(
Context.Client(
clientName = clientName,
clientVersion = clientVersion,
osVersion = osVersion,
gl = locale.gl,
hl = locale.hl,
visitorData = visitorData,
Expand All @@ -29,11 +31,12 @@ data class YouTubeClient(

private const val USER_AGENT_WEB = "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"
const val USER_AGENT_IOS = "com.google.ios.youtube/19.29.1 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X;)"

val ANDROID_MUSIC =
YouTubeClient(
clientName = "ANDROID_MUSIC",
clientVersion = "5.01",
clientVersion = "5.54.52",
api_key = "AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI",
userAgent = USER_AGENT_ANDROID,
)
Expand All @@ -45,7 +48,14 @@ data class YouTubeClient(
api_key = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w",
userAgent = USER_AGENT_ANDROID,
)

val IOS =
YouTubeClient(
clientName = "IOS",
clientVersion = "19.29.1",
osVersion = "17.5.1.21F90",
api_key = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc",
userAgent = USER_AGENT_IOS,
)
val WEB =
YouTubeClient(
clientName = "WEB",
Expand All @@ -57,7 +67,7 @@ data class YouTubeClient(
val WEB_REMIX =
YouTubeClient(
clientName = "WEB_REMIX",
clientVersion = "1.20220606.03.00",
clientVersion = "1.20241023.01.00",
api_key = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30",
userAgent = USER_AGENT_WEB,
referer = REFERER_YOUTUBE_MUSIC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,16 @@ data class PlayerBody(
val context: Context,
val videoId: String,
val playlistId: String?,
)
val cpn: String? = "wzf9Y0nqz6AUe2Vr", // need some random cpn to get same algorithm for sig
val playbackContext: PlaybackContext? = PlaybackContext(ContentPlaybackContext(20019L)),
) {
@Serializable
data class PlaybackContext(
val contentPlaybackContext: ContentPlaybackContext?,
)

@Serializable
data class ContentPlaybackContext(
val signatureTimestamp: Long?,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.malopieds.innertube.models.response

import com.malopieds.innertube.InnerTube
import com.malopieds.innertube.models.ResponseContext
import com.malopieds.innertube.models.Thumbnails
import kotlinx.serialization.Serializable
Expand Down Expand Up @@ -57,9 +58,12 @@ data class PlayerResponse(
val audioChannels: Int?,
val loudnessDb: Double?,
val lastModified: Long?,
val signatureCipher: String?,
) {
val isAudio: Boolean
get() = width == null

fun findUrl() = url ?: signatureCipher?.let { InnerTube().decodeCipher(it) }!!
}
}

Expand Down
66 changes: 66 additions & 0 deletions innertube/src/main/java/com/malopieds/innertube/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,69 @@ fun String.parseTime(): Int? {
}
return null
}

fun nSigDecode(n: String): String {
val step1 =
buildString {
append(n[8])
append(n.substring(2, 8))
append(n[1])
append(n.substring(9))
}

val step2 =
buildString {
append(step1.substring(7))
append((step1[0] + step1.substring(1, 3).reversed() + step1[3]).reversed())
append(step1.substring(4, 7))
}

val step3 = step2.substring(7) + step2.substring(0, 7)

val step4 =
buildString {
append(step3[step3.length - 4])
append(step3.substring(3, 7))
append(step3[2])
append(step3.substring(8, 11))
append(step3[7])
append(step3.takeLast(3))
append(step3[1])
}

val step5 = (step4.substring(0, 2) + step4.last() + step4.substring(3, step4.length - 1) + step4[2]).reversed()

val keyString = "cbrrC5"
val charset = ('A'..'Z') + ('a'..'z') + ('0'..'9') + listOf('-', '_')
val mutableKeyList = keyString.toMutableList()

val transformedChars = CharArray(step5.length)

for (index in step5.indices) {
val currentChar = step5[index]
val indexInCharset =
(charset.indexOf(currentChar) - charset.indexOf(mutableKeyList[index % mutableKeyList.size]) + index + charset.size - index) %
charset.size
transformedChars[index] = charset[indexInCharset]
mutableKeyList[index % mutableKeyList.size] = transformedChars[index]
}

val step6 = String(transformedChars)
return step6.dropLast(3).reversed() + step6.takeLast(3)
}

fun sigDecode(input: String): String {
val middleSection = input.substring(3, input.length - 3)
val rearranged = (middleSection.take(35) + input[0] + middleSection.drop(36)).reversed()
val result =
buildString {
append("A")
append(rearranged.substring(0, 15))
append(input[input.length - 2])
append(rearranged.substring(16, 34))
append(input[input.length - 3])
append(rearranged.substring(35))
append(input[38])
}
return result
}

0 comments on commit 6b2318a

Please sign in to comment.