diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f8140fe..125b1f4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,7 +61,7 @@ android { } dependencies { - val libVersion = "dd65bd4709" + val libVersion = "38e1df03f6" compileOnly("com.github.brahmkshatriya:echo:$libVersion") implementation("com.squareup.okhttp3:okhttp:4.12.0") diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt b/app/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt index 20c7fef..d782f9f 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt @@ -41,7 +41,7 @@ import org.apache.http.conn.ConnectTimeoutException import java.util.Locale class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, SearchClient, AlbumClient, ArtistClient, - PlaylistClient, ShareClient, LoginClient.WebView, LoginClient.UsernamePassword, LoginClient.CustomTextInput, + PlaylistClient, ShareClient, LoginClient.WebView.Cookie, LoginClient.UsernamePassword, LoginClient.CustomTextInput, LibraryClient { private val json = Json { @@ -376,27 +376,53 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, SearchClie } else { DeezerApi().getMediaUrl(track, useFlac) } - val dataObject = jsonObject["data"]!!.jsonArray.first().jsonObject - val mediaObject = dataObject["media"]!!.jsonArray.first().jsonObject - val sourcesObject = mediaObject["sources"]!!.jsonArray[0] - val url = sourcesObject.jsonObject["url"]!!.jsonPrimitive.content val key = Utils.createBlowfishKey(trackId = track.id) - Track( - id = track.id, - title = track.title, - cover = track.cover, - artists = track.artists, - audioStreamables = listOf( - Streamable( - id = url, - quality = 0, - extra = mapOf( - "key" to key + if(jsonObject.toString().contains("Track token has no sufficient rights on requested media")) { + val trackObject = DeezerApi().track(arrayOf(track)) + val resultObject = trackObject["results"]!!.jsonObject + val dataObject = resultObject["data"]!!.jsonArray[0].jsonObject + val md5Origin = dataObject["MD5_ORIGIN"]?.jsonPrimitive?.content ?: "" + val mediaVersion = dataObject["MEDIA_VERSION"]?.jsonPrimitive?.content ?: "" + val url = generateTrackUrl(track.id, md5Origin, mediaVersion, 1) + + Track( + id = track.id, + title = track.title, + cover = track.cover, + artists = track.artists, + audioStreamables = listOf( + Streamable( + id = url, + quality = 0, + extra = mapOf( + "key" to key + ) ) ) ) - ) + } else { + val dataObject = jsonObject["data"]!!.jsonArray.first().jsonObject + val mediaObject = dataObject["media"]!!.jsonArray.first().jsonObject + val sourcesObject = mediaObject["sources"]!!.jsonArray[0] + val url = sourcesObject.jsonObject["url"]!!.jsonPrimitive.content + + Track( + id = track.id, + title = track.title, + cover = track.cover, + artists = track.artists, + audioStreamables = listOf( + Streamable( + id = url, + quality = 0, + extra = mapOf( + "key" to key + ) + ) + ) + ) + } } override fun getMediaItems(track: Track): PagedData = getMediaItems(track.artists.first()) @@ -517,10 +543,10 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, SearchClie override val loginWebViewStopUrlRegex = "https://www\\.deezer\\.com/account/.*".toRegex() - override suspend fun onLoginWebviewStop(url: String, cookie: String): List { - if (cookie.contains("arl=")) { - DeezerCredentials.arl = cookie.substringAfter("arl=").substringBefore(";") - DeezerCredentials.sid = cookie.substringAfter("sid=").substringBefore(";") + override suspend fun onLoginWebviewStop(url: String, data: String): List { + if (data.contains("arl=")) { + DeezerCredentials.arl = data.substringAfter("arl=").substringBefore(";") + DeezerCredentials.sid = data.substringAfter("sid=").substringBefore(";") val userList = DeezerApi().makeUser() return userList } else { diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extension/Utils.kt b/app/src/main/java/dev/brahmkshatriya/echo/extension/Utils.kt index 9c18f71..a176d40 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extension/Utils.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extension/Utils.kt @@ -11,6 +11,7 @@ import okhttp3.Request import java.io.ByteArrayOutputStream import java.math.BigInteger import java.security.MessageDigest +import java.util.Arrays import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -151,6 +152,55 @@ private fun decryptStreamChunk(chunk: ByteArray, key: String): ByteArray { return decryptedBytes } +fun generateTrackUrl(trackId: String, md5Origin: String, mediaVersion: String, quality: Int): String { + val magic = 164 + val step1 = ByteArrayOutputStream() + step1.write(md5Origin.toByteArray()) + step1.write(164) + step1.write(quality.toString().toByteArray()) + step1.write(magic) + step1.write(trackId.toByteArray()) + step1.write(magic) + step1.write(mediaVersion.toByteArray()) + + val md5 = MessageDigest.getInstance("MD5") + md5.update(step1.toByteArray()) + val digest = md5.digest() + val md5hex = bytesToHexTrack(digest).lowercase() + + val step2 = ByteArrayOutputStream() + step2.write(md5hex.toByteArray()) + step2.write(magic) + step2.write(step1.toByteArray()) + step2.write(magic) + + while (step2.size()%16 > 0) step2.write(46) + + val cipher = Cipher.getInstance("AES/ECB/NoPadding") + val key = SecretKeySpec("jo6aey6haid2Teih".toByteArray(), "AES") + cipher.init(Cipher.ENCRYPT_MODE, key) + + val step3 = StringBuilder() + for (i in 0 until step2.size() / 16) { + val b = Arrays.copyOfRange(step2.toByteArray(), i*16, (i+1)*16) + step3.append(bytesToHexTrack(cipher.doFinal(b)).lowercase()) + } + + val url = "https://e-cdns-proxy-" + md5Origin[0] + ".dzcdn.net/mobile/1/" + step3.toString() + return url +} + +private fun bytesToHexTrack(bytes: ByteArray): String { + val HEX_ARRAY = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(bytes.size * 2) + for (j in bytes.indices) { + val v = bytes[j].toInt() and 0xFF + hexChars[j * 2] = HEX_ARRAY[v ushr 4] + hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F] + } + return String(hexChars) +} + fun moveElement(array: Array, fromIndex: Int, toIndex: Int): Array { if (fromIndex !in array.indices || toIndex !in array.indices) { throw IndexOutOfBoundsException("Index out of bounds")