diff --git a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt index 1b7eb918..ec3e78fd 100644 --- a/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt +++ b/common/core-components/src/commonMain/kotlin/com.shabinder.common.core_components/utils/NetworkingExt.kt @@ -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(url,block).call.request.url.toString() + }.getOrNull() ?: url + } +} + fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient { // https://github.com/Kotlin/kotlinx.serialization/issues/1450 install(JsonFeature) { diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt index 4267ca97..10cc35a0 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/DownloadObject.kt @@ -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" } diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/SoundCloudTrack.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/SoundCloudTrack.kt deleted file mode 100644 index f660e4dd..00000000 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/SoundCloudTrack.kt +++ /dev/null @@ -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? { - 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 - } - } -} \ No newline at end of file diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Track.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Track.kt deleted file mode 100644 index 32237bad..00000000 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/Track.kt +++ /dev/null @@ -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 = "" -) \ No newline at end of file diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt index 583f96fc..ddc9c128 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/soundcloud/resolvemodel/SoundCloudResolveResponseBase.kt @@ -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 = emptyList(), + var tracks: List = 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? { + 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 + } + } + } } \ No newline at end of file diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt index fbaa3f7d..b20fe1d6 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/spotify/Source.kt @@ -20,5 +20,6 @@ enum class Source { Spotify, YouTube, Gaana, - JioSaavn + JioSaavn, + SoundCloud } diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt index c2bceb41..87658110 100644 --- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt +++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/FetchPlatformQueryResult.kt @@ -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", diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt index 44c67ea3..18e250a2 100644 --- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt +++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/ProvidersModule.kt @@ -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()) } } diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt index 2f551d17..f78dc085 100644 --- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt +++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/SoundCloudProvider.kt @@ -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(trackDetails.videoID.requireNotNull()).getString("url") + } + + + private fun List.toTrackDetailsList( + type: String, + subFolder: String + ): List = + 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(".") } } \ No newline at end of file diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt index 615b7f4e..004e49b4 100644 --- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt +++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/sound_cloud/requests/SoundCloudRequests.kt @@ -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}" }), diff --git a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt index c7706153..5f08bafd 100644 --- a/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt +++ b/common/providers/src/commonMain/kotlin/com.shabinder.common.providers/spotify/SpotifyProvider.kt @@ -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" ) } diff --git a/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt b/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt index aa55719d..a06d8471 100644 --- a/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt +++ b/common/providers/src/commonTest/kotlin/com/shabinder/common/providers/TestSpotifyTrackMatching.kt @@ -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) }