mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-26 02:24:31 +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
|
package utils
|
||||||
|
|
||||||
|
import jiosaavn.JioSaavnRequests
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
// Test Class- at development Time
|
// 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 {
|
kotlin {
|
||||||
js() {
|
js {
|
||||||
//useCommonJs()
|
//useCommonJs()
|
||||||
browser {
|
browser {
|
||||||
webpackTask {
|
webpackTask {
|
||||||
|
Loading…
Reference in New Issue
Block a user