package it.vfsfitvnm.youtubemusic import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* import io.ktor.client.plugins.compression.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import it.vfsfitvnm.youtubemusic.models.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json object YouTube { private const val Key = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" @OptIn(ExperimentalSerializationApi::class) val client = HttpClient(CIO) { BrowserUserAgent() expectSuccess = true install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true explicitNulls = false encodeDefaults = true }) } install(ContentEncoding) { brotli() } defaultRequest { url("https://music.youtube.com") } } @Serializable data class BrowseBody( val context: Context, val browseId: String, ) @Serializable data class SearchBody( val context: Context, val query: String, val params: String ) @Serializable data class PlayerBody( val context: Context, val videoId: String, val playlistId: String? ) @Serializable data class GetQueueBody( val context: Context, val videoIds: List?, val playlistId: String?, ) @Serializable data class NextBody( val context: Context, val isAudioOnly: Boolean, val videoId: String, val playlistId: String?, val tunerSettingValue: String, val index: Int?, val params: String?, val playlistSetVideoId: String?, val continuation: String?, val watchEndpointMusicSupportedConfigs: WatchEndpointMusicSupportedConfigs ) { @Serializable data class WatchEndpointMusicSupportedConfigs( val musicVideoType: String ) } @Serializable data class GetSearchSuggestionsBody( val context: Context, val input: String ) @Serializable data class Context( val client: Client, val thirdParty: ThirdParty? = null, ) { @Serializable data class Client( val clientName: String, val clientVersion: String, val visitorData: String?, // val gl: String = "US", val hl: String = "en", ) @Serializable data class ThirdParty( val embedUrl: String, ) companion object { val DefaultWeb = Context( client = Client( clientName = "WEB_REMIX", clientVersion = "1.20220328.01.00", visitorData = "CgtsZG1ySnZiQWtSbyiMjuGSBg%3D%3D" ) ) val DefaultAndroid = Context( client = Client( clientName = "ANDROID", clientVersion = "16.50", visitorData = null, ) ) } } data class Info( val name: String, val endpoint: T? ) { companion object { inline fun from(run: Runs.Run): Info { return Info( name = run.text, endpoint = run.navigationEndpoint?.endpoint as T? ) } } } sealed class Item { abstract val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail? data class Song( val info: Info, val authors: List>, val album: Info?, val durationText: String?, override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail? ) : Item() { companion object : FromMusicShelfRendererContent { val Filter = Filter("EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D") override fun from(content: MusicShelfRenderer.Content): Song { val (mainRuns, otherRuns) = content.runs return Song( info = Info.from(mainRuns.first()), authors = otherRuns .getOrNull(otherRuns.lastIndex - 2) ?.map(Info.Companion::from) ?: emptyList(), album = otherRuns .getOrNull(otherRuns.lastIndex - 1) ?.firstOrNull() ?.let(Info.Companion::from), durationText = otherRuns .getOrNull(otherRuns.lastIndex) ?.firstOrNull()?.text, thumbnail = content .thumbnail ) } } } data class Video( val info: Info, val authors: List>, val views: List>, val durationText: String?, override val thumbnail: ThumbnailRenderer.MusicThumbnailRenderer.Thumbnail.Thumbnail? ) : Item() { val isOfficialMusicVideo: Boolean get() = info .endpoint ?.watchEndpointMusicSupportedConfigs ?.watchEndpointMusicConfig ?.musicVideoType == "MUSIC_VIDEO_TYPE_OMV" val isUserGeneratedContent: Boolean get() = info .endpoint ?.watchEndpointMusicSupportedConfigs ?.watchEndpointMusicConfig ?.musicVideoType == "MUSIC_VIDEO_TYPE_UGC" companion object : FromMusicShelfRendererContent