mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
JIO-Saavn (WIP)
This commit is contained in:
parent
28d3b53582
commit
67361b1337
51
maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt
Normal file
51
maintenance-tasks/src/main/java/jiosaavn/JioSaavnRequests.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package jiosaavn
|
||||
|
||||
import analytics_html_img.client
|
||||
import io.ktor.client.request.forms.FormDataContent
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.http.Parameters
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
interface JioSaavnRequests {
|
||||
|
||||
fun searchForSong(
|
||||
queryURL: String
|
||||
) {
|
||||
}
|
||||
|
||||
suspend fun getSong(
|
||||
ID: String,
|
||||
fetchLyrics: Boolean = false
|
||||
): JsonObject {
|
||||
return ((Json.parseToJsonElement(client.get(song_details_base_url + ID)) as JsonObject)[ID] as JsonObject)
|
||||
.formatData(fetchLyrics)
|
||||
}
|
||||
|
||||
suspend fun getSongID(
|
||||
queryURL: String,
|
||||
fetchLyrics: Boolean = false
|
||||
): String? {
|
||||
val res = client.get<String>(queryURL) {
|
||||
body = FormDataContent(
|
||||
Parameters.build {
|
||||
append("bitrate", "320")
|
||||
}
|
||||
)
|
||||
}
|
||||
return try {
|
||||
res.split("\"song\":{\"type\":\"")[1].split("\",\"image\":")[0].split("\"id\":\"").last()
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
res.split("\"pid\":\"").getOrNull(1)?.split("\",\"")?.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// EndPoints
|
||||
const val search_base_url = "https://www.jiosaavn.com/api.php?__call=autocomplete.get&_format=json&_marker=0&cc=in&includeMetaTags=1&query="
|
||||
const val song_details_base_url = "https://www.jiosaavn.com/api.php?__call=song.getDetails&cc=in&_marker=0%3F_marker%3D0&_format=json&pids="
|
||||
const val album_details_base_url = "https://www.jiosaavn.com/api.php?__call=content.getAlbumDetails&_format=json&cc=in&_marker=0%3F_marker%3D0&albumid="
|
||||
const val playlist_details_base_url = "https://www.jiosaavn.com/api.php?__call=playlist.getDetails&_format=json&cc=in&_marker=0%3F_marker%3D0&listid="
|
||||
const val lyrics_base_url = "https://www.jiosaavn.com/api.php?__call=lyrics.getLyrics&ctx=web6dot0&api_version=4&_format=json&_marker=0%3F_marker%3D0&lyrics_id="
|
||||
}
|
||||
}
|
89
maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt
Normal file
89
maintenance-tasks/src/main/java/jiosaavn/JioSaavnUtils.kt
Normal file
@ -0,0 +1,89 @@
|
||||
package jiosaavn
|
||||
|
||||
import io.ktor.util.InternalAPI
|
||||
import io.ktor.util.decodeBase64Bytes
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import utils.unescape
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.DESKeySpec
|
||||
|
||||
internal fun JsonObject.formatData(
|
||||
includeLyrics: Boolean = false
|
||||
): JsonObject {
|
||||
return buildJsonObject {
|
||||
// Accommodate Incoming Json Object Data
|
||||
// And `Format` everything while iterating
|
||||
this@formatData.forEach {
|
||||
if (it.value is JsonPrimitive && it.value.jsonPrimitive.isString) {
|
||||
put(it.key, it.value.jsonPrimitive.content.format())
|
||||
} else {
|
||||
put(it.key, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var url = getString("media_preview_url")!!.replace("preview", "aac") // We Will catch NPE
|
||||
url = if (getBoolean("320kbps") == true) {
|
||||
url.replace("_96_p.mp4", "_320.mp4")
|
||||
} else {
|
||||
url.replace("_96_p.mp4", "_160.mp4")
|
||||
}
|
||||
// Add Media URL to JSON Object
|
||||
put("media_url", url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// DECRYPT Encrypted Media URL
|
||||
getString("encrypted_media_url")?.let {
|
||||
put("media_url", decryptURL(it))
|
||||
}
|
||||
// Check if 320 Kbps is available or not
|
||||
if (getBoolean("320kbps") != true && containsKey("media_url")) {
|
||||
put("media_url", getString("media_url")?.replace("_320.mp4", "_160.mp4"))
|
||||
}
|
||||
}
|
||||
|
||||
put("image", getString("image")?.replace("150x150", "500x500"))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(InternalAPI::class)
|
||||
fun decryptURL(url: String): String {
|
||||
val dks = DESKeySpec("38346591".toByteArray())
|
||||
val keyFactory = SecretKeyFactory.getInstance("DES")
|
||||
val key: SecretKey = keyFactory.generateSecret(dks)
|
||||
|
||||
val cipher: Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding").apply {
|
||||
init(Cipher.DECRYPT_MODE, key, SecureRandom())
|
||||
}
|
||||
|
||||
return cipher.doFinal(url.decodeBase64Bytes())
|
||||
.decodeToString()
|
||||
.replace("_96.mp4", "_320.mp4")
|
||||
}
|
||||
|
||||
internal fun String.format(): String {
|
||||
return this.unescape()
|
||||
.replace(""", "'")
|
||||
.replace("&", "&")
|
||||
.replace("'", "'")
|
||||
}
|
||||
|
||||
fun JsonObject.getString(key: String): String? = this[key]?.jsonPrimitive?.content
|
||||
fun JsonObject.getLong(key: String): Long = this[key]?.jsonPrimitive?.content?.toLongOrNull() ?: 0
|
||||
fun JsonObject.getInteger(key: String): Int = this[key]?.jsonPrimitive?.content?.toIntOrNull() ?: 0
|
||||
fun JsonObject.getBoolean(key: String): Boolean? = this[key]?.jsonPrimitive?.content?.toBoolean()
|
||||
fun JsonObject.getFloat(key: String): Float? = this[key]?.jsonPrimitive?.content?.toFloatOrNull()
|
||||
fun JsonObject.getDouble(key: String): Double? = this[key]?.jsonPrimitive?.content?.toDoubleOrNull()
|
||||
fun JsonObject?.getJsonObject(key: String): JsonObject? = this?.get(key)?.jsonObject
|
||||
fun JsonArray?.getJsonObject(index: Int): JsonObject? = this?.get(index)?.jsonObject
|
||||
fun JsonObject?.getJsonArray(key: String): JsonArray? = this?.get(key)?.jsonArray
|
92
maintenance-tasks/src/main/java/utils/JsonUtils.kt
Normal file
92
maintenance-tasks/src/main/java/utils/JsonUtils.kt
Normal file
@ -0,0 +1,92 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
* JSON UTILS
|
||||
* */
|
||||
fun String.escape(): String {
|
||||
val output = StringBuilder()
|
||||
for (element in this) {
|
||||
val chx = element.toInt()
|
||||
assert(chx != 0)
|
||||
when {
|
||||
element == '\n' -> {
|
||||
output.append("\\n")
|
||||
}
|
||||
element == '\t' -> {
|
||||
output.append("\\t")
|
||||
}
|
||||
element == '\r' -> {
|
||||
output.append("\\r")
|
||||
}
|
||||
element == '\\' -> {
|
||||
output.append("\\\\")
|
||||
}
|
||||
element == '"' -> {
|
||||
output.append("\\\"")
|
||||
}
|
||||
element == '\b' -> {
|
||||
output.append("\\b")
|
||||
}
|
||||
chx >= 0x10000 -> {
|
||||
assert(false) { "Java stores as u16, so it should never give us a character that's bigger than 2 bytes. It literally can't." }
|
||||
}
|
||||
chx > 127 -> {
|
||||
output.append(String.format("\\u%04x", chx))
|
||||
}
|
||||
else -> {
|
||||
output.append(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
return output.toString()
|
||||
}
|
||||
|
||||
fun String.unescape(): String {
|
||||
val builder = StringBuilder()
|
||||
var i = 0
|
||||
while (i < this.length) {
|
||||
val delimiter = this[i]
|
||||
i++ // consume letter or backslash
|
||||
if (delimiter == '\\' && i < this.length) {
|
||||
|
||||
// consume first after backslash
|
||||
val ch = this[i]
|
||||
i++
|
||||
when (ch) {
|
||||
'\\', '/', '"', '\'' -> {
|
||||
builder.append(ch)
|
||||
}
|
||||
'n' -> builder.append('\n')
|
||||
'r' -> builder.append('\r')
|
||||
't' -> builder.append(
|
||||
'\t'
|
||||
)
|
||||
'b' -> builder.append('\b')
|
||||
'f' -> builder.append("\\f")
|
||||
'u' -> {
|
||||
val hex = StringBuilder()
|
||||
|
||||
// expect 4 digits
|
||||
if (i + 4 > this.length) {
|
||||
throw RuntimeException("Not enough unicode digits! ")
|
||||
}
|
||||
for (x in this.substring(i, i + 4).toCharArray()) {
|
||||
if (!Character.isLetterOrDigit(x)) {
|
||||
throw RuntimeException("Bad character in unicode escape.")
|
||||
}
|
||||
hex.append(Character.toLowerCase(x))
|
||||
}
|
||||
i += 4 // consume those four digits.
|
||||
val code = hex.toString().toInt(16)
|
||||
builder.append(code.toChar())
|
||||
}
|
||||
else -> {
|
||||
throw RuntimeException("Illegal escape sequence: \\$ch")
|
||||
}
|
||||
}
|
||||
} else { // it's not a backslash, or it's the last character.
|
||||
builder.append(delimiter)
|
||||
}
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
@ -1,6 +1,14 @@
|
||||
package utils
|
||||
|
||||
import jiosaavn.JioSaavnRequests
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
// Test Class- at development Time
|
||||
fun main() = runBlocking {}
|
||||
fun main() = runBlocking {
|
||||
val jioSaavnClient = object : JioSaavnRequests {}
|
||||
val resp = jioSaavnClient.getSongID(
|
||||
queryURL = "https://www.jiosaavn.com/song/nadiyon-paar-let-the-music-play-again-from-roohi/KAM0bj1AAn4"
|
||||
)
|
||||
|
||||
debug(jioSaavnClient.getSong(resp.toString()).toString())
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ dependencies {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
js() {
|
||||
js {
|
||||
//useCommonJs()
|
||||
browser {
|
||||
webpackTask {
|
||||
|
Loading…
Reference in New Issue
Block a user