mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-24 18:04:33 +01:00
SoundCloud Impl
This commit is contained in:
parent
de4c84bdda
commit
7f279f2602
@ -8,6 +8,8 @@ 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.statement.HttpResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.native.concurrent.SharedImmutable
|
||||
|
||||
@ -23,6 +25,18 @@ suspend fun isInternetAccessible(): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
// If Fails returns Input Url
|
||||
suspend inline fun HttpClient.getFinalUrl(
|
||||
url: String,
|
||||
crossinline block: HttpRequestBuilder.() -> Unit = {}
|
||||
): String {
|
||||
return withContext(dispatcherIO) {
|
||||
runCatching {
|
||||
get<HttpResponse>(url,block).call.request.url.toString()
|
||||
}.getOrNull() ?: url
|
||||
}
|
||||
}
|
||||
|
||||
fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
|
||||
// https://github.com/Kotlin/kotlinx.serialization/issues/1450
|
||||
install(JsonFeature) {
|
||||
|
@ -44,7 +44,7 @@ data class TrackDetails(
|
||||
var audioQuality: AudioQuality = AudioQuality.KBPS192,
|
||||
var audioFormat: AudioFormat = AudioFormat.MP4,
|
||||
var outputFilePath: String, // UriString in Android
|
||||
var videoID: String? = null,
|
||||
var videoID: String? = null, // will be used for purposes like Downloadable Link || VideoID etc. based on Provider
|
||||
) : Parcelable {
|
||||
val outputMp3Path get() = outputFilePath.substringBeforeLast(".") + ".mp3"
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
package com.shabinder.common.models.soundcloud
|
||||
|
||||
|
||||
import com.shabinder.common.models.AudioFormat
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SoundCloudTrack(
|
||||
@SerialName("artwork_url")
|
||||
val artworkUrl: String = "",
|
||||
//val caption: Any = Any(),
|
||||
@SerialName("comment_count")
|
||||
val commentCount: Int = 0,
|
||||
val commentable: Boolean = false,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String = "", //2015-05-21T16:36:39Z
|
||||
val description: String = "",
|
||||
@SerialName("display_date")
|
||||
val displayDate: String = "",
|
||||
@SerialName("download_count")
|
||||
val downloadCount: Int = 0,
|
||||
val downloadable: Boolean = false,
|
||||
val duration: Int = 0, //290116
|
||||
@SerialName("embeddable_by")
|
||||
val embeddableBy: String = "",
|
||||
@SerialName("full_duration")
|
||||
val fullDuration: Int = 0,
|
||||
val genre: String = "",
|
||||
@SerialName("has_downloads_left")
|
||||
val hasDownloadsLeft: Boolean = false,
|
||||
val id: Int = 0,
|
||||
val kind: String = "",
|
||||
@SerialName("label_name")
|
||||
val labelName: String = "",
|
||||
@SerialName("last_modified")
|
||||
val lastModified: String = "",
|
||||
val license: String = "",
|
||||
@SerialName("likes_count")
|
||||
val likesCount: Int = 0,
|
||||
val media: Media = Media(), // Important Data
|
||||
@SerialName("monetization_model")
|
||||
val monetizationModel: String = "",
|
||||
val permalink: String = "",
|
||||
@SerialName("permalink_url")
|
||||
val permalinkUrl: String = "",
|
||||
@SerialName("playback_count")
|
||||
val playbackCount: Int = 0,
|
||||
val policy: String = "",
|
||||
val `public`: Boolean = false,
|
||||
@SerialName("publisher_metadata")
|
||||
val publisherMetadata: PublisherMetadata = PublisherMetadata(),
|
||||
//@SerialName("purchase_title")
|
||||
//val purchaseTitle: Any = Any(),
|
||||
@SerialName("purchase_url")
|
||||
val purchaseUrl: String = "", //"http://itunes.apple.com/us/album/sunrise-ep/id993328519"
|
||||
@SerialName("release_date")
|
||||
val releaseDate: String = "",
|
||||
@SerialName("reposts_count")
|
||||
val repostsCount: Int = 0,
|
||||
//@SerialName("secret_token")
|
||||
//val secretToken: Any = Any(),
|
||||
val sharing: String = "",
|
||||
val state: String = "",
|
||||
@SerialName("station_permalink")
|
||||
val stationPermalink: String = "",
|
||||
@SerialName("station_urn")
|
||||
val stationUrn: String = "",
|
||||
val streamable: Boolean = false,
|
||||
@SerialName("tag_list")
|
||||
val tagList: String = "",
|
||||
val title: String = "",
|
||||
@SerialName("track_authorization")
|
||||
val trackAuthorization: String = "",
|
||||
@SerialName("track_format")
|
||||
val trackFormat: String = "",
|
||||
val uri: String = "",
|
||||
val urn: String = "",
|
||||
val user: User = User(),
|
||||
@SerialName("user_id")
|
||||
val userId: Int = 0,
|
||||
//val visuals: Any = Any(),
|
||||
@SerialName("waveform_url")
|
||||
val waveformUrl: String = ""
|
||||
) {
|
||||
fun getDownloadableLink(): Pair<String, AudioFormat>? {
|
||||
return (media.transcodings.firstOrNull {
|
||||
it.quality == "hq" && (it.format.isProgressive || it.url.contains("progressive"))
|
||||
} ?: media.transcodings.firstOrNull {
|
||||
it.quality == "sq" && (it.format.isProgressive || it.url.contains("progressive"))
|
||||
})?.let {
|
||||
it.url to it.audioFormat
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package com.shabinder.common.models.soundcloud
|
||||
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Track(
|
||||
@SerialName("artwork_url")
|
||||
val artworkUrl: String = "",
|
||||
val caption: String = "",
|
||||
@SerialName("comment_count")
|
||||
val commentCount: Int = 0,
|
||||
val commentable: Boolean = false,
|
||||
@SerialName("created_at")
|
||||
val createdAt: String = "",
|
||||
val description: String = "",
|
||||
@SerialName("display_date")
|
||||
val displayDate: String = "",
|
||||
@SerialName("download_count")
|
||||
val downloadCount: Int = 0,
|
||||
val downloadable: Boolean = false,
|
||||
val duration: Int = 0,
|
||||
@SerialName("embeddable_by")
|
||||
val embeddableBy: String = "",
|
||||
@SerialName("full_duration")
|
||||
val fullDuration: Int = 0,
|
||||
val genre: String = "",
|
||||
@SerialName("has_downloads_left")
|
||||
val hasDownloadsLeft: Boolean = false,
|
||||
val id: Int = 0,
|
||||
val kind: String = "",
|
||||
@SerialName("label_name")
|
||||
val labelName: String = "",
|
||||
@SerialName("last_modified")
|
||||
val lastModified: String = "",
|
||||
val license: String = "",
|
||||
@SerialName("likes_count")
|
||||
val likesCount: Int = 0,
|
||||
val media: Media = Media(),
|
||||
@SerialName("monetization_model")
|
||||
val monetizationModel: String = "",
|
||||
val permalink: String = "",
|
||||
@SerialName("permalink_url")
|
||||
val permalinkUrl: String = "",
|
||||
@SerialName("playback_count")
|
||||
val playbackCount: Int = 0,
|
||||
val policy: String = "",
|
||||
val `public`: Boolean = false,
|
||||
@SerialName("publisher_metadata")
|
||||
val publisherMetadata: PublisherMetadata = PublisherMetadata(),
|
||||
@SerialName("purchase_title")
|
||||
val purchaseTitle:String = "",
|
||||
@SerialName("purchase_url")
|
||||
val purchaseUrl: String = "",
|
||||
@SerialName("release_date")
|
||||
val releaseDate: String = "",
|
||||
@SerialName("reposts_count")
|
||||
val repostsCount: Int = 0,
|
||||
@SerialName("secret_token")
|
||||
val secretToken: String = "",
|
||||
val sharing: String = "",
|
||||
val state: String = "",
|
||||
@SerialName("station_permalink")
|
||||
val stationPermalink: String = "",
|
||||
@SerialName("station_urn")
|
||||
val stationUrn: String = "",
|
||||
val streamable: Boolean = false,
|
||||
@SerialName("tag_list")
|
||||
val tagList: String = "",
|
||||
val title: String = "",
|
||||
@SerialName("track_authorization")
|
||||
val trackAuthorization: String = "",
|
||||
@SerialName("track_format")
|
||||
val trackFormat: String = "",
|
||||
val uri: String = "",
|
||||
val urn: String = "",
|
||||
val user: User = User(),
|
||||
@SerialName("user_id")
|
||||
val userId: Int = 0,
|
||||
val visuals: String = "",
|
||||
@SerialName("waveform_url")
|
||||
val waveformUrl: String = ""
|
||||
)
|
@ -1,15 +1,12 @@
|
||||
package com.shabinder.common.models.soundcloud.resolvemodel
|
||||
|
||||
import com.shabinder.common.models.AudioFormat
|
||||
import com.shabinder.common.models.soundcloud.Media
|
||||
import com.shabinder.common.models.soundcloud.PublisherMetadata
|
||||
import com.shabinder.common.models.soundcloud.Track
|
||||
import com.shabinder.common.models.soundcloud.User
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
@Serializable
|
||||
@JsonClassDiscriminator("kind")
|
||||
@ -69,7 +66,7 @@ sealed class SoundCloudResolveResponseBase {
|
||||
val title: String = "", //"Top 50: Hip-hop & Rap"
|
||||
@SerialName("track_count")
|
||||
val trackCount: Int = 0,
|
||||
val tracks: List<Track> = emptyList(),
|
||||
var tracks: List<SoundCloudResolveResponseTrack> = emptyList(),
|
||||
val uri: String = "",
|
||||
val user: User = User(),
|
||||
@SerialName("user_id")
|
||||
@ -155,5 +152,15 @@ sealed class SoundCloudResolveResponseBase {
|
||||
val visuals: String = "",
|
||||
@SerialName("waveform_url")
|
||||
val waveformUrl: String = ""
|
||||
) : SoundCloudResolveResponseBase()
|
||||
) : SoundCloudResolveResponseBase() {
|
||||
fun getDownloadableLink(): Pair<String, AudioFormat>? {
|
||||
return (media.transcodings.firstOrNull {
|
||||
it.quality == "hq" && (it.format.isProgressive || it.url.contains("progressive"))
|
||||
} ?: media.transcodings.firstOrNull {
|
||||
it.quality == "sq" && (it.format.isProgressive || it.url.contains("progressive"))
|
||||
})?.let {
|
||||
it.url to it.audioFormat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,5 +20,6 @@ enum class Source {
|
||||
Spotify,
|
||||
YouTube,
|
||||
Gaana,
|
||||
JioSaavn
|
||||
JioSaavn,
|
||||
SoundCloud
|
||||
}
|
||||
|
@ -20,7 +20,12 @@ import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.core_components.file_manager.FileManager
|
||||
import com.shabinder.common.core_components.preference_manager.PreferenceManager
|
||||
import com.shabinder.common.database.DownloadRecordDatabaseQueries
|
||||
import com.shabinder.common.models.*
|
||||
import com.shabinder.common.models.AudioFormat
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.PlatformQueryResult
|
||||
import com.shabinder.common.models.SpotiFlyerException
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.dispatcherIO
|
||||
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
||||
import com.shabinder.common.models.event.coroutines.flatMapError
|
||||
import com.shabinder.common.models.event.coroutines.onSuccess
|
||||
@ -28,9 +33,9 @@ import com.shabinder.common.models.event.coroutines.success
|
||||
import com.shabinder.common.models.spotify.Source
|
||||
import com.shabinder.common.providers.gaana.GaanaProvider
|
||||
import com.shabinder.common.providers.saavn.SaavnProvider
|
||||
import com.shabinder.common.providers.sound_cloud.SoundCloudProvider
|
||||
import com.shabinder.common.providers.spotify.SpotifyProvider
|
||||
import com.shabinder.common.providers.youtube.YoutubeProvider
|
||||
import com.shabinder.common.providers.youtube.get
|
||||
import com.shabinder.common.providers.youtube_music.YoutubeMusic
|
||||
import com.shabinder.common.providers.youtube_to_mp3.requests.YoutubeMp3
|
||||
import com.shabinder.common.utils.appendPadded
|
||||
@ -45,6 +50,7 @@ class FetchPlatformQueryResult(
|
||||
private val spotifyProvider: SpotifyProvider,
|
||||
private val youtubeProvider: YoutubeProvider,
|
||||
private val saavnProvider: SaavnProvider,
|
||||
private val soundCloudProvider: SoundCloudProvider,
|
||||
private val youtubeMusic: YoutubeMusic,
|
||||
private val youtubeMp3: YoutubeMp3,
|
||||
val fileManager: FileManager,
|
||||
@ -66,7 +72,7 @@ class FetchPlatformQueryResult(
|
||||
link.contains("youtube.com", true) || link.contains("youtu.be", true) ->
|
||||
youtubeProvider.query(link)
|
||||
|
||||
// Jio Saavn
|
||||
// JioSaavn
|
||||
link.contains("saavn", true) ->
|
||||
saavnProvider.query(link)
|
||||
|
||||
@ -74,6 +80,10 @@ class FetchPlatformQueryResult(
|
||||
link.contains("gaana", true) ->
|
||||
gaanaProvider.query(link)
|
||||
|
||||
// SoundCloud
|
||||
link.contains("soundcloud", true) ->
|
||||
soundCloudProvider.query(link)
|
||||
|
||||
else -> {
|
||||
SuspendableEvent.error(SpotiFlyerException.LinkInvalid(link))
|
||||
}
|
||||
@ -122,7 +132,7 @@ class FetchPlatformQueryResult(
|
||||
ytMp3Link.component2()?.stackTraceToString()
|
||||
?: "couldn't fetch link for ${track.videoID} ,trying manual extraction"
|
||||
)
|
||||
appendLine("Trying Local Extraction")
|
||||
//appendLine("Trying Local Extraction")
|
||||
null
|
||||
} else {
|
||||
audioFormat = AudioFormat.MP3
|
||||
@ -130,6 +140,20 @@ class FetchPlatformQueryResult(
|
||||
}
|
||||
}
|
||||
}
|
||||
Source.SoundCloud -> {
|
||||
audioFormat = track.audioFormat
|
||||
soundCloudProvider.getDownloadURL(track).let {
|
||||
if (it is SuspendableEvent.Failure || it.component1().isNullOrEmpty()) {
|
||||
appendPadded(
|
||||
"SoundCloud Provider Failed for ${track.title}:",
|
||||
it.component2()?.stackTraceToString()
|
||||
?: "couldn't fetch link for ${track.trackUrl}"
|
||||
)
|
||||
null
|
||||
} else
|
||||
it.component1()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
appendPadded(
|
||||
"Invalid Arguments",
|
||||
|
@ -2,6 +2,7 @@ package com.shabinder.common.providers
|
||||
|
||||
import com.shabinder.common.providers.gaana.GaanaProvider
|
||||
import com.shabinder.common.providers.saavn.SaavnProvider
|
||||
import com.shabinder.common.providers.sound_cloud.SoundCloudProvider
|
||||
import com.shabinder.common.providers.spotify.SpotifyProvider
|
||||
import com.shabinder.common.providers.spotify.token_store.TokenStore
|
||||
import com.shabinder.common.providers.youtube.YoutubeProvider
|
||||
@ -16,7 +17,8 @@ fun providersModule(enableNetworkLogs: Boolean) = module {
|
||||
single { GaanaProvider(get(), get(), get()) }
|
||||
single { SaavnProvider(get(), get(), get()) }
|
||||
single { YoutubeProvider(get(), get(), get()) }
|
||||
single { SoundCloudProvider(get(), get(), get()) }
|
||||
single { YoutubeMp3(get(), get()) }
|
||||
single { YoutubeMusic(get(), get(), get(), get(), get()) }
|
||||
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
|
@ -2,12 +2,115 @@ package com.shabinder.common.providers.sound_cloud
|
||||
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.core_components.file_manager.FileManager
|
||||
import com.shabinder.common.core_components.file_manager.finalOutputDir
|
||||
import com.shabinder.common.models.AudioFormat
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.PlatformQueryResult
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponsePlaylist
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase.SoundCloudResolveResponseTrack
|
||||
import com.shabinder.common.models.spotify.Source
|
||||
import com.shabinder.common.providers.sound_cloud.requests.SoundCloudRequests
|
||||
import com.shabinder.common.providers.sound_cloud.requests.doAuthenticatedRequest
|
||||
import com.shabinder.common.utils.requireNotNull
|
||||
import io.github.shabinder.utils.getString
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
class SoundCloudProvider(
|
||||
private val logger: Kermit,
|
||||
private val fileManager: FileManager,
|
||||
) {
|
||||
suspend fun query(fullURL: String) {
|
||||
override val httpClient: HttpClient,
|
||||
) : SoundCloudRequests {
|
||||
suspend fun query(fullURL: String) = SuspendableEvent {
|
||||
PlatformQueryResult(
|
||||
folderType = "",
|
||||
subFolder = "",
|
||||
title = "",
|
||||
coverUrl = "",
|
||||
trackList = listOf(),
|
||||
Source.SoundCloud
|
||||
).apply {
|
||||
when (val response = fetchResult(fullURL)) {
|
||||
is SoundCloudResolveResponseTrack -> {
|
||||
folderType = "Tracks"
|
||||
subFolder = ""
|
||||
trackList = listOf(response).toTrackDetailsList(folderType, subFolder)
|
||||
coverUrl = response.artworkUrl
|
||||
title = response.title
|
||||
}
|
||||
is SoundCloudResolveResponsePlaylist -> {
|
||||
folderType = "Playlists"
|
||||
subFolder = response.title
|
||||
trackList = response.tracks.toTrackDetailsList(folderType, subFolder)
|
||||
coverUrl = response.artworkUrl.ifBlank { response.calculatedArtworkUrl }
|
||||
title = response.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getDownloadURL(trackDetails: TrackDetails) = SuspendableEvent {
|
||||
doAuthenticatedRequest<JsonObject>(trackDetails.videoID.requireNotNull()).getString("url")
|
||||
}
|
||||
|
||||
|
||||
private fun List<SoundCloudResolveResponseTrack>.toTrackDetailsList(
|
||||
type: String,
|
||||
subFolder: String
|
||||
): List<TrackDetails> =
|
||||
map {
|
||||
val downloadableInfo = it.getDownloadableLink()
|
||||
TrackDetails(
|
||||
title = it.title,
|
||||
//trackNumber = it.track_number,
|
||||
genre = listOf(it.genre),
|
||||
artists = /*it.artists?.map { artist -> artist?.name.toString() } ?:*/ listOf(it.user.username.ifBlank { it.genre }),
|
||||
albumArtists = /*it.album?.artists?.mapNotNull { artist -> artist?.name } ?:*/ emptyList(),
|
||||
durationSec = (it.duration / 1000),
|
||||
albumArtPath = fileManager.imageCacheDir() + (it.artworkUrl.formatArtworkUrl()).substringAfterLast(
|
||||
'/'
|
||||
) + ".jpeg",
|
||||
albumName = "", //it.album?.name,
|
||||
year = runCatching { it.displayDate.substring(0, 4) }.getOrNull(),
|
||||
comment = it.caption,
|
||||
trackUrl = it.permalinkUrl,
|
||||
downloaded = it.updateStatusIfPresent(type, subFolder),
|
||||
source = Source.SoundCloud,
|
||||
albumArtURL = it.artworkUrl.formatArtworkUrl(),
|
||||
outputFilePath = fileManager.finalOutputDir(
|
||||
it.title,
|
||||
type,
|
||||
subFolder,
|
||||
fileManager.defaultDir()/*,".m4a"*/
|
||||
),
|
||||
audioQuality = AudioQuality.KBPS128,
|
||||
videoID = downloadableInfo?.first,
|
||||
audioFormat = downloadableInfo?.second ?: AudioFormat.MP3
|
||||
)
|
||||
}
|
||||
|
||||
private fun SoundCloudResolveResponseTrack.updateStatusIfPresent(
|
||||
folderType: String,
|
||||
subFolder: String
|
||||
): DownloadStatus {
|
||||
return if (fileManager.isPresent(
|
||||
fileManager.finalOutputDir(
|
||||
title,
|
||||
folderType,
|
||||
subFolder,
|
||||
fileManager.defaultDir()
|
||||
)
|
||||
)
|
||||
) { // Download Already Present!!
|
||||
DownloadStatus.Downloaded
|
||||
} else
|
||||
DownloadStatus.NotDownloaded
|
||||
}
|
||||
|
||||
private fun String.formatArtworkUrl(): String {
|
||||
return substringBeforeLast("-") + "-t500x500." + substringAfterLast(".")
|
||||
}
|
||||
}
|
@ -1,31 +1,38 @@
|
||||
package com.shabinder.common.providers.sound_cloud.requests
|
||||
|
||||
import com.shabinder.common.core_components.utils.getFinalUrl
|
||||
import com.shabinder.common.models.SpotiFlyerException
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
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 io.github.shabinder.utils.getBoolean
|
||||
import io.github.shabinder.utils.getString
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.features.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
interface SoundCloudRequests {
|
||||
|
||||
val httpClient: HttpClient
|
||||
|
||||
suspend fun fetchResult(url: String): SoundCloudResolveResponseBase {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var url = url
|
||||
|
||||
suspend fun parseURL(url: String) {
|
||||
getResponseObj(url).let { item ->
|
||||
when (item) {
|
||||
// Fetch Full URL if Input is Shortened URL from App
|
||||
if (url.contains("soundcloud.app"))
|
||||
url = httpClient.getFinalUrl(url)
|
||||
|
||||
return getResponseObj(url).run {
|
||||
when (this) {
|
||||
is SoundCloudResolveResponseTrack -> {
|
||||
getTrack(item)
|
||||
getTrack()
|
||||
}
|
||||
is SoundCloudResolveResponsePlaylist -> {
|
||||
|
||||
populatePlaylist()
|
||||
}
|
||||
else -> throw SpotiFlyerException.FeatureNotImplementedYet()
|
||||
}
|
||||
@ -33,8 +40,8 @@ interface SoundCloudRequests {
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
suspend fun getTrack(track: SoundCloudResolveResponseTrack): TrackDetails? {
|
||||
val track = getTrackInfo(track)
|
||||
suspend fun SoundCloudResolveResponseTrack.getTrack() = apply {
|
||||
val track = populateTrackInfo()
|
||||
|
||||
if (track.policy == "BLOCK")
|
||||
throw SpotiFlyerException.GeoLocationBlocked(extraInfo = "Use VPN to access ${track.title}")
|
||||
@ -42,21 +49,38 @@ interface SoundCloudRequests {
|
||||
if (!track.streamable)
|
||||
throw SpotiFlyerException.LinkInvalid("\nSound Cloud Reports that ${track.title} is not streamable !\n")
|
||||
|
||||
return null
|
||||
return track
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
suspend fun SoundCloudResolveResponsePlaylist.populatePlaylist(): SoundCloudResolveResponsePlaylist = apply {
|
||||
supervisorScope {
|
||||
try {
|
||||
tracks = tracks.map {
|
||||
async {
|
||||
runCatching {
|
||||
it.populateTrackInfo()
|
||||
}.getOrNull() ?: it
|
||||
}
|
||||
}.awaitAll()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun getTrackInfo(res: SoundCloudResolveResponseTrack): SoundCloudResolveResponseTrack {
|
||||
if (res.media.transcodings.isNotEmpty())
|
||||
return res
|
||||
private suspend fun SoundCloudResolveResponseTrack.populateTrackInfo(): SoundCloudResolveResponseTrack {
|
||||
if (media.transcodings.isNotEmpty())
|
||||
return this
|
||||
|
||||
val infoURL = URLS.TRACK_INFO.buildURL(res.id.toString())
|
||||
val infoURL = URLS.TRACK_INFO.buildURL(id.toString())
|
||||
return httpClient.get(infoURL) {
|
||||
parameter("client_id", CLIENT_ID)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getResponseObj(url: String, clientID: String = CLIENT_ID): SoundCloudResolveResponseBase {
|
||||
private suspend fun getResponseObj(url: String, clientID: String = CLIENT_ID): SoundCloudResolveResponseBase {
|
||||
val itemURL = URLS.RESOLVE.buildURL(url)
|
||||
val resp: SoundCloudResolveResponseBase = try {
|
||||
httpClient.get(itemURL) {
|
||||
@ -75,6 +99,7 @@ interface SoundCloudRequests {
|
||||
return resp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
companion object {
|
||||
private enum class URLS(val buildURL: (arg: String) -> String) {
|
||||
RESOLVE({ "https://api-v2.soundcloud.com/resolve?url=$it}" }),
|
||||
|
@ -79,7 +79,7 @@ class SpotifyProvider(
|
||||
|
||||
if (type == "episode" || type == "show") {
|
||||
throw SpotiFlyerException.FeatureNotImplementedYet(
|
||||
"Support for Spotify's ${type.uppercase()} isn't implemented yet"
|
||||
"Support for Spotify's ${type.toUpperCase()} isn't implemented yet"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
package com.shabinder.common.providers
|
||||
|
||||
import com.shabinder.common.core_components.utils.createHttpClient
|
||||
import com.shabinder.common.core_components.utils.getFinalUrl
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.common.models.soundcloud.SoundCloudTrack
|
||||
import com.shabinder.common.models.soundcloud.resolvemodel.SoundCloudResolveResponseBase
|
||||
import com.shabinder.common.providers.utils.CommonUtils
|
||||
import com.shabinder.common.providers.utils.SpotifyUtils
|
||||
import com.shabinder.common.providers.utils.SpotifyUtils.toTrackDetailsList
|
||||
import com.shabinder.common.utils.globalJson
|
||||
import io.github.shabinder.runBlocking
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.serializer
|
||||
import kotlin.test.Test
|
||||
|
||||
class TestSpotifyTrackMatching {
|
||||
@ -27,12 +27,8 @@ class TestSpotifyTrackMatching {
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
@Test
|
||||
fun testRandomThing() = runBlocking {
|
||||
globalJson.decodeFromString(SoundCloudResolveResponseBase.serializer(), """{"artwork_url":null,"trackCount":12,"kind":"playlist"}""")
|
||||
.also {
|
||||
println(it)
|
||||
println(it is SoundCloudResolveResponseBase.SoundCloudResolveResponsePlaylist)
|
||||
println(it is SoundCloudResolveResponseBase.SoundCloudResolveResponseTrack)
|
||||
}
|
||||
val res = createHttpClient().getFinalUrl("https://soundcloud.app.goo.gl/vrBzR")
|
||||
println(res)
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user