Skip to content

Commit

Permalink
THE HOLY COMMIT
Browse files Browse the repository at this point in the history
  • Loading branch information
LuftVerbot committed May 19, 2024
1 parent 1d2165f commit 418cf04
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 175 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
-Pandroid.injected.signing.store.password=${{ secrets.PASSWORD }} \
-Pandroid.injected.signing.key.alias=key0 \
-Pandroid.injected.signing.key.password=${{ secrets.PASSWORD }}
cp app/build/outputs/apk/debug/app-debug.apk app/build/yt-${{ env.VERSION }}.apk
cp app/build/outputs/apk/debug/app-debug.apk app/build/de-${{ env.VERSION }}.apk
- name: Upload APK
uses: actions/upload-artifact@v4
Expand Down
8 changes: 0 additions & 8 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,11 @@ dependencies {
val libVersion = "728a3edac8"
compileOnly("com.github.brahmkshatriya:echo:$libVersion")

implementation("org.nanohttpd:nanohttpd:2.3.1")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jsoup:jsoup:1.16.1")

implementation("com.google.code.gson:gson:2.8.8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.room:room-ktx:2.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.0")
implementation("androidx.room:room-runtime:2.3.0")
annotationProcessor("androidx.room:room-compiler:2.3.0")

testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1-Beta")
Expand Down
Binary file added app/release/app-release.apk
Binary file not shown.
20 changes: 20 additions & 0 deletions app/release/output-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "dev.brahmkshatriya.echo.extension.deezer",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dev.brahmkshatriya.echo.extension

import android.util.Log
import dev.brahmkshatriya.echo.common.models.Album
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.ImageHolder
Expand Down
112 changes: 84 additions & 28 deletions app/src/main/java/dev/brahmkshatriya/echo/extension/DeezerExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dev.brahmkshatriya.echo.common.clients.HomeFeedClient
import dev.brahmkshatriya.echo.common.clients.LoginClient
import dev.brahmkshatriya.echo.common.clients.PlaylistClient
import dev.brahmkshatriya.echo.common.clients.TrackClient
import dev.brahmkshatriya.echo.common.exceptions.LoginRequiredException
import dev.brahmkshatriya.echo.common.helpers.PagedData
import dev.brahmkshatriya.echo.common.models.Album
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
Expand All @@ -15,22 +16,33 @@ import dev.brahmkshatriya.echo.common.models.Playlist
import dev.brahmkshatriya.echo.common.models.Request
import dev.brahmkshatriya.echo.common.models.Request.Companion.toRequest
import dev.brahmkshatriya.echo.common.models.Streamable
import dev.brahmkshatriya.echo.common.models.StreamableAudio.Companion.toAudio
import dev.brahmkshatriya.echo.common.models.StreamableAudio
import dev.brahmkshatriya.echo.common.models.StreamableVideo
import dev.brahmkshatriya.echo.common.models.Tab
import dev.brahmkshatriya.echo.common.models.Track
import dev.brahmkshatriya.echo.common.models.User
import dev.brahmkshatriya.echo.common.settings.Setting
import dev.brahmkshatriya.echo.common.settings.Settings
import dev.brahmkshatriya.echo.extension.network.MultiStreamServer
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.net.ServerSocket
import okhttp3.OkHttpClient
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.PipedInputStream
import java.io.PipedOutputStream
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, AlbumClient, PlaylistClient, LoginClient.WebView {

Expand Down Expand Up @@ -72,7 +84,7 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, AlbumClien

override suspend fun getHomeTabs(): List<Tab> {
if(arl == null) {
throw Exception("Please login to Deezer!!")
throw LoginRequiredException("", "Deezer")
} else {
val resultObject = DeezerApi(arl!!, sid!!, token!!, userId!!).homePage()
val name = resultObject["title"]!!.jsonPrimitive.content
Expand Down Expand Up @@ -104,44 +116,88 @@ class DeezerExtension : ExtensionClient, HomeFeedClient, TrackClient, AlbumClien
}
//<============= Play =============>

override suspend fun getStreamableAudio(streamable: Streamable) = streamable.id.toAudio()
private val client = OkHttpClient()

override suspend fun getStreamableAudio(streamable: Streamable) = getByteStreamAudio(streamable)

private fun getByteStreamAudio(streamable: Streamable): StreamableAudio {
val url = streamable.id
val contentLength = getContentLength(url)
val key = streamable.extra["key"]!!

val request = okhttp3.Request.Builder().url(url).build()
var decChunk = ByteArray(0)
with(client.newCall(request).execute()) {
val byteStream = this.body?.byteStream()

// Read the entire byte stream into memory
val completeStream = ByteArrayOutputStream()
val buffer = ByteArray(16384)
var bytesRead: Int
while (byteStream?.read(buffer).also { bytesRead = it ?: -1 } != -1) {
completeStream.write(buffer, 0, bytesRead)
}

// Decrypt the complete stream
val completeStreamBytes = completeStream.toByteArray()

var place = 0

while (place < completeStreamBytes.size) {
val remainingBytes = completeStreamBytes.size - place
val chunkSize = if (remainingBytes > 2048 * 3) 2048 * 3 else remainingBytes
val decryptingChunk = completeStreamBytes.copyOfRange(place, place + chunkSize)
place += chunkSize

var decryptedChunk = ByteArray(0)
if (decryptingChunk.size > 2048) {
decryptedChunk = Utils.decryptBlowfish(decryptingChunk.copyOfRange(0, 2048), key)
decryptedChunk += decryptingChunk.copyOfRange(2048, decryptingChunk.size)
}
decChunk += decryptedChunk
}
}

return StreamableAudio.ByteStreamAudio(
stream = decChunk.inputStream(),
totalBytes = contentLength
)
}



private fun getContentLength(url: String): Long {
var totalLength = 0L
val request = okhttp3.Request.Builder().url(url).head().build()
val response = client.newCall(request).execute()
totalLength += response.header("Content-Length")?.toLong() ?: 0L
response.close()
return totalLength
}

override suspend fun getStreamableVideo(streamable: Streamable) =
StreamableVideo(Request(streamable.id), looping = false, crop = false)

override suspend fun loadTrack(track: Track) = coroutineScope {
val jsonObject = DeezerApi(arl!!, sid!!, token!!, userId!!, licenseToken!!).getMediaUrl(track)
val dataObject = jsonObject["data"]!!.jsonArray.first().jsonObject
val mediaObject = dataObject["media"]!!.jsonArray.first().jsonObject
val sourcesArray = mediaObject["sources"]!!.jsonArray
val urls = mutableListOf<String>()
sourcesArray.mapIndexed { index, jsonElement ->
when (index) {
0 -> urls.add(jsonElement.jsonObject["url"]!!.jsonPrimitive.content)
1 -> urls.add(jsonElement.jsonObject["url"]!!.jsonPrimitive.content)
else -> {}
}
}
urls.forEach {
Log.d("homePage", it)
}

/*val trackObject = DeezerApi(arl!!, sid!!, token!!, userId!!).track(track)
val resultObject = trackObject["results"]!!.jsonObject
val trackDataObject = resultObject["data"]!!.jsonArray.first().jsonObject*/
val sourcesObject = mediaObject["sources"]!!.jsonArray[1]
val url = sourcesObject.jsonObject["url"]!!.jsonPrimitive.content
val key = Utils.createBlowfishKey(trackId = track.id)

val serverSocket = ServerSocket(0)
val port = serverSocket.localPort
serverSocket.close()
val server = MultiStreamServer(urls ,port = port)
server.start()
Track(
id = track.id,
title = track.title,
cover = track.cover,
audioStreamables = listOf(
Streamable(
id = "http://127.0.0.1:$port",
quality = 0),
id = url,
quality = 0,
extra = mapOf(
"key" to key
)
)
)
)
}
Expand Down
78 changes: 78 additions & 0 deletions app/src/main/java/dev/brahmkshatriya/echo/extension/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.brahmkshatriya.echo.extension

import android.webkit.URLUtil
import java.io.IOException
import java.math.BigInteger
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

object Utils {
private const val secret = "g4el58wc0zvf9na1"
private val secretIvSpec = IvParameterSpec(byteArrayOf(0,1,2,3,4,5,6,7))

@Throws(IOException::class)
fun getFinalURL(url: String): String {
val con: HttpURLConnection = URL(url).openConnection() as HttpURLConnection
con.instanceFollowRedirects = false
con.connect()
con.inputStream
if (con.responseCode == HttpURLConnection.HTTP_MOVED_PERM || con.responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
val redirectUrl: String = con.getHeaderField("Location")
if (URLUtil.isValidUrl(redirectUrl)) return getFinalURL(redirectUrl)
}
return url
}

private fun bitwiseXor(firstVal: Char, secondVal: Char, thirdVal: Char): Char {
return (BigInteger(byteArrayOf(firstVal.code.toByte())) xor
BigInteger(byteArrayOf(secondVal.code.toByte())) xor
BigInteger(byteArrayOf(thirdVal.code.toByte()))).toByte().toInt().toChar()
}

fun createBlowfishKey(trackId: String): String {
val trackMd5Hex = trackId.toMD5()
var blowfishKey = ""

for (i in 0..15) {
val nextChar = bitwiseXor(trackMd5Hex[i], trackMd5Hex[i + 16], secret[i])
blowfishKey += nextChar
}

return blowfishKey
}

fun decryptBlowfish(chunk: ByteArray, blowfishKey: String): ByteArray {
val secretKeySpec = SecretKeySpec(blowfishKey.toByteArray(), "Blowfish")
val thisTrackCipher = Cipher.getInstance("BLOWFISH/CBC/NoPadding")
thisTrackCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, secretIvSpec)
return thisTrackCipher.update(chunk)
}

fun decryptBlowfishFinal(chunk: ByteArray, blowfishKey: String, isFinal: Boolean): ByteArray {
val secretKeySpec = SecretKeySpec(blowfishKey.toByteArray(), "Blowfish")
val thisTrackCipher = Cipher.getInstance("BLOWFISH/CBC/NoPadding")
thisTrackCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, secretIvSpec)
return if (isFinal) {
thisTrackCipher.doFinal(chunk)
} else {
thisTrackCipher.update(chunk)
}
}
}

private fun bytesToHex(bytes: ByteArray): String {
var hexString = ""
for (byte in bytes) {
hexString += String.format("%02X", byte)
}
return hexString
}

fun String.toMD5(): String {
val bytes = MessageDigest.getInstance("MD5").digest(this.toByteArray(Charsets.ISO_8859_1))
return bytesToHex(bytes).lowercase()
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 418cf04

Please sign in to comment.