mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-27 19:04:32 +01:00
SoundCloud Parsing Fixes and HttpClient SSL Certificates Validation Ignore
This commit is contained in:
parent
d5838db298
commit
9b3b00f0d2
@ -55,6 +55,7 @@ android {
|
||||
targetSdk = Versions.targetSdkVersion
|
||||
versionCode = Versions.versionCode
|
||||
versionName = Versions.versionName
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
|
@ -26,9 +26,9 @@ import org.gradle.kotlin.dsl.getByType
|
||||
|
||||
object Versions {
|
||||
// App's Version (To be bumped at each update)
|
||||
const val versionName = "3.5.0"
|
||||
const val versionName = "3.6.0"
|
||||
|
||||
const val versionCode = 27
|
||||
const val versionCode = 28
|
||||
|
||||
// Android
|
||||
const val minSdkVersion = 21
|
||||
@ -78,6 +78,7 @@ val VersionCatalog.kotlinCoroutines get() = findDependency("kotlin-coroutines").
|
||||
val VersionCatalog.kotlinxSerialization get() = findDependency("kotlinx-serialization-json").get()
|
||||
val VersionCatalog.ktorClientIOS get() = findDependency("ktor-client-ios").get()
|
||||
val VersionCatalog.ktorClientAndroid get() = findDependency("ktor-client-android").get()
|
||||
val VersionCatalog.ktorClientAndroidOkHttp get() = findDependency("ktor-client-okhttp").get()
|
||||
val VersionCatalog.ktorClientApache get() = findDependency("ktor-client-apache").get()
|
||||
val VersionCatalog.ktorClientJS get() = findDependency("ktor-client-js").get()
|
||||
val VersionCatalog.ktorClientCIO get() = findDependency("ktor-client-cio").get()
|
||||
|
@ -58,6 +58,7 @@ ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version
|
||||
ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization", version.ref = "ktor" }
|
||||
ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" }
|
||||
ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
|
||||
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
|
||||
ktor-client-curl = { group = "io.ktor", name = "ktor-client-curl", version.ref = "ktor" }
|
||||
ktor-client-apache = { group = "io.ktor", name = "ktor-client-apache", version.ref = "ktor" }
|
||||
ktor-client-ios = { group = "io.ktor", name = "ktor-client-ios", version.ref = "ktor" }
|
||||
|
@ -67,7 +67,7 @@ kotlin {
|
||||
implementation(compose.materialIconsExtended)
|
||||
implementation(Deps.androidXCommonBundle)
|
||||
implementation(Deps.decomposeComposeExt)
|
||||
implementation(Deps.ktorClientAndroid)
|
||||
implementation(Deps.ktorClientAndroidOkHttp)
|
||||
implementation(Deps.koinAndroidBundle)
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ class AndroidFileManager(
|
||||
// Get Memory Efficient Bitmap
|
||||
val bitmap: Bitmap? = getMemoryEfficientBitmap(input, reqWidth, reqHeight)
|
||||
|
||||
parallelExecutor.executeSuspending {
|
||||
parallelExecutor.execute {
|
||||
// Decode and Cache Full Sized Image in Background
|
||||
cacheImage(
|
||||
BitmapFactory.decodeByteArray(input, 0, input.size),
|
||||
|
@ -40,7 +40,7 @@ fun Mp3File.setId3v1Tags(track: TrackDetails): Mp3File {
|
||||
title = track.title
|
||||
album = track.albumName
|
||||
year = track.year
|
||||
comment = "Genres:${track.comment}"
|
||||
comment = "${track.comment}"
|
||||
if (track.trackNumber != null)
|
||||
this.track = track.trackNumber.toString()
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package com.shabinder.common.core_components.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.HttpClientConfig
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.engine.okhttp.OkHttpConfig
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
actual fun buildHttpClient(extraConfig: HttpClientConfig<*>.() -> Unit): HttpClient {
|
||||
return HttpClient(OkHttp) {
|
||||
engine {
|
||||
preconfigured = getUnsafeOkHttpClient()
|
||||
}
|
||||
extraConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnsafeOkHttpClient(): OkHttpClient {
|
||||
return try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
val trustAllCerts: TrustManager = object : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||
}
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
val sslContext: SSLContext = SSLContext.getInstance("SSL").apply {
|
||||
init(null, arrayOf(trustAllCerts), SecureRandom())
|
||||
}
|
||||
|
||||
OkHttpClient.Builder().run {
|
||||
sslSocketFactory(sslContext.socketFactory, trustAllCerts as X509TrustManager)
|
||||
hostnameVerifier { _, _ -> true }
|
||||
followRedirects(true)
|
||||
build()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
@ -40,15 +40,6 @@ interface ParallelProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> executeSafelyInPool(
|
||||
onComplete: suspend (result: SuspendableEvent<T, Throwable>) -> Unit = {},
|
||||
block: suspend () -> T
|
||||
): SuspendableEvent<T, Throwable> {
|
||||
return SuspendableEvent {
|
||||
parallelExecutor.executeSuspending(block)
|
||||
}.also { onComplete(it) }
|
||||
}
|
||||
|
||||
suspend fun stopAllTasks() {
|
||||
parallelExecutor.closeAndReInit()
|
||||
}
|
||||
|
@ -2,14 +2,20 @@ package com.shabinder.common.core_components.utils
|
||||
|
||||
import com.shabinder.common.models.dispatcherIO
|
||||
import com.shabinder.common.utils.globalJson
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.features.logging.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.HttpClientConfig
|
||||
import io.ktor.client.features.HttpTimeout
|
||||
import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||
import io.ktor.client.features.logging.DEFAULT
|
||||
import io.ktor.client.features.logging.LogLevel
|
||||
import io.ktor.client.features.logging.Logger
|
||||
import io.ktor.client.features.logging.Logging
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.head
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import io.ktor.http.ContentType
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.native.concurrent.SharedImmutable
|
||||
|
||||
@ -32,12 +38,13 @@ suspend inline fun HttpClient.getFinalUrl(
|
||||
): String {
|
||||
return withContext(dispatcherIO) {
|
||||
runCatching {
|
||||
get<HttpResponse>(url,block).call.request.url.toString()
|
||||
get<HttpResponse>(url, block).call.request.url.toString()
|
||||
}.getOrNull() ?: url
|
||||
}
|
||||
}
|
||||
|
||||
fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
|
||||
buildHttpClient {
|
||||
// https://github.com/Kotlin/kotlinx.serialization/issues/1450
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(globalJson)
|
||||
@ -63,8 +70,10 @@ fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
|
||||
level = LogLevel.INFO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect fun buildHttpClient(extraConfig: HttpClientConfig<*>.() -> Unit): HttpClient
|
||||
|
||||
/*Client Active Throughout App's Lifetime*/
|
||||
@SharedImmutable
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.shabinder.common.core_components.utils
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.HttpClientConfig
|
||||
import io.ktor.client.engine.apache.Apache
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy
|
||||
import org.apache.http.ssl.SSLContextBuilder
|
||||
|
||||
actual fun buildHttpClient(extraConfig: HttpClientConfig<*>.() -> Unit): HttpClient {
|
||||
return HttpClient(Apache) {
|
||||
engine {
|
||||
customizeClient {
|
||||
setSSLContext(
|
||||
SSLContextBuilder
|
||||
.create()
|
||||
.loadTrustMaterial(TrustSelfSignedStrategy())
|
||||
.build()
|
||||
)
|
||||
setSSLHostnameVerifier(NoopHostnameVerifier())
|
||||
}
|
||||
}
|
||||
extraConfig()
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.shabinder.common.core_components.utils
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.HttpClientConfig
|
||||
import io.ktor.client.engine.js.Js
|
||||
|
||||
actual fun buildHttpClient(extraConfig: HttpClientConfig<*>.() -> Unit): HttpClient = HttpClient(Js) {
|
||||
extraConfig()
|
||||
}
|
@ -31,7 +31,7 @@ sealed class SoundCloudResolveResponseBase {
|
||||
@SerialName("embeddable_by")
|
||||
val embeddableBy: String = "",
|
||||
val genre: String = "",
|
||||
val id: Int = 0,
|
||||
val id: String = "",
|
||||
@SerialName("is_album")
|
||||
val isAlbum: Boolean = false,
|
||||
@SerialName("label_name")
|
||||
@ -100,7 +100,7 @@ sealed class SoundCloudResolveResponseBase {
|
||||
val genre: String = "",
|
||||
@SerialName("has_downloads_left")
|
||||
val hasDownloadsLeft: Boolean = false,
|
||||
val id: Int = 0,
|
||||
val id: String = "",
|
||||
override val kind: String = "",
|
||||
@SerialName("label_name")
|
||||
val labelName: String = "",
|
||||
|
@ -16,5 +16,5 @@ val globalJson by lazy {
|
||||
* Removing Illegal Chars from File Name
|
||||
* **/
|
||||
fun removeIllegalChars(fileName: String): String {
|
||||
return fileName.replace("[^\\dA-Za-z_]".toRegex(), "_")
|
||||
return fileName.replace("[^\\dA-Za-z0-9-_]".toRegex(), "_")
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import com.shabinder.common.models.SpotiFlyerException
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponsePlaylist
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponseTrack
|
||||
import com.shabinder.common.utils.globalJson
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.client.request.get
|
||||
@ -12,7 +13,13 @@ import io.ktor.client.request.parameter
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
interface SoundCloudRequests {
|
||||
|
||||
@ -74,18 +81,21 @@ interface SoundCloudRequests {
|
||||
if (media.transcodings.isNotEmpty())
|
||||
return this
|
||||
|
||||
val infoURL = URLS.TRACK_INFO.buildURL(id.toString())
|
||||
return httpClient.get(infoURL) {
|
||||
val infoURL = URLS.TRACK_INFO.buildURL(id)
|
||||
|
||||
val data: String = httpClient.get(infoURL) {
|
||||
parameter("client_id", CLIENT_ID)
|
||||
}
|
||||
return globalJson.decodeFromString(data)
|
||||
}
|
||||
|
||||
private suspend fun getResponseObj(url: String, clientID: String = CLIENT_ID): SoundCloudResolveResponseBase {
|
||||
val itemURL = URLS.RESOLVE.buildURL(url)
|
||||
val resp: SoundCloudResolveResponseBase = try {
|
||||
httpClient.get(itemURL) {
|
||||
val data: String = httpClient.get(itemURL) {
|
||||
parameter("client_id", clientID)
|
||||
}
|
||||
globalJson.decodeFromString(SoundCloudSerializer, data)
|
||||
} catch (e: ClientRequestException) {
|
||||
if (clientID != ALT_CLIENT_ID)
|
||||
return getResponseObj(url, ALT_CLIENT_ID)
|
||||
@ -116,6 +126,25 @@ interface SoundCloudRequests {
|
||||
|
||||
const val CLIENT_ID = "a3e059563d7fd3372b49b37f00a00bcf"
|
||||
const val ALT_CLIENT_ID = "2t9loNQH90kzJcsFCODdigxfp325aq4z"
|
||||
|
||||
object SoundCloudSerializer :
|
||||
JsonContentPolymorphicSerializer<SoundCloudResolveResponseBase>(SoundCloudResolveResponseBase::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out SoundCloudResolveResponseBase> =
|
||||
when {
|
||||
"track_count" in element.jsonObject -> SoundCloudResolveResponsePlaylist.serializer()
|
||||
"kind" in element.jsonObject -> {
|
||||
val isTrack =
|
||||
element.jsonObject["kind"]
|
||||
?.jsonPrimitive?.content.toString()
|
||||
.contains("track", true)
|
||||
when {
|
||||
isTrack || "track_format" in element.jsonObject -> SoundCloudResolveResponseTrack.serializer()
|
||||
else -> SoundCloudResolveResponsePlaylist.serializer()
|
||||
}
|
||||
}
|
||||
else -> SoundCloudResolveResponsePlaylist.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user