package it.vfsfitvnm.youtubemusic import io.ktor.client.HttpClient import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.plugins.BrowserUserAgent import io.ktor.client.plugins.compression.ContentEncoding import io.ktor.client.plugins.compression.brotli import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.header import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.serialization.kotlinx.json.json import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint import it.vfsfitvnm.youtubemusic.models.Runs import it.vfsfitvnm.youtubemusic.models.Thumbnail import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json object Innertube { val client = HttpClient(OkHttp) { BrowserUserAgent() expectSuccess = true install(ContentNegotiation) { @OptIn(ExperimentalSerializationApi::class) json(Json { ignoreUnknownKeys = true explicitNulls = false encodeDefaults = true }) } install(ContentEncoding) { brotli() } defaultRequest { url(scheme = "https", host ="music.youtube.com") { headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString()) headers.append("X-Goog-Api-Key", "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8") parameters.append("prettyPrint", "false") } } } internal const val browse = "/youtubei/v1/browse" internal const val next = "/youtubei/v1/next" internal const val player = "/youtubei/v1/player" internal const val queue = "/youtubei/v1/music/get_queue" internal const val search = "/youtubei/v1/search" internal const val searchSuggestions = "/youtubei/v1/music/get_search_suggestions" internal const val musicResponsiveListItemRendererMask = "musicResponsiveListItemRenderer(flexColumns,fixedColumns,thumbnail,navigationEndpoint)" internal const val musicTwoRowItemRendererMask = "musicTwoRowItemRenderer(thumbnailRenderer,title,subtitle,navigationEndpoint)" const val playlistPanelVideoRendererMask = "playlistPanelVideoRenderer(title,navigationEndpoint,longBylineText,shortBylineText,thumbnail,lengthText)" internal fun HttpRequestBuilder.mask(value: String = "*") = header("X-Goog-FieldMask", value) data class Info( val name: String?, val endpoint: T? ) { @Suppress("UNCHECKED_CAST") constructor(run: Runs.Run) : this( name = run.text, endpoint = run.navigationEndpoint?.endpoint as T? ) } @JvmInline value class SearchFilter(val value: String) { companion object { val Song = SearchFilter("EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D") val Video = SearchFilter("EgWKAQIQAWoKEAkQChAFEAMQBA%3D%3D") val Album = SearchFilter("EgWKAQIYAWoKEAkQChAFEAMQBA%3D%3D") val Artist = SearchFilter("EgWKAQIgAWoKEAkQChAFEAMQBA%3D%3D") val CommunityPlaylist = SearchFilter("EgeKAQQoAEABagoQAxAEEAoQCRAF") val FeaturedPlaylist = SearchFilter("EgeKAQQoADgBagwQDhAKEAMQBRAJEAQ%3D") } } sealed class Item { abstract val thumbnail: Thumbnail? abstract val key: String } data class SongItem( val info: Info?, val authors: List>?, val album: Info?, val durationText: String?, override val thumbnail: Thumbnail? ) : Item() { override val key get() = info!!.endpoint!!.videoId!! companion object } data class VideoItem( val info: Info?, val authors: List>?, val viewsText: String?, val durationText: String?, override val thumbnail: Thumbnail? ) : Item() { override val key get() = info!!.endpoint!!.videoId!! 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 } data class AlbumItem( val info: Info?, val authors: List>?, val year: String?, override val thumbnail: Thumbnail? ) : Item() { override val key get() = info!!.endpoint!!.browseId!! companion object } data class ArtistItem( val info: Info?, val subscribersCountText: String?, override val thumbnail: Thumbnail? ) : Item() { override val key get() = info!!.endpoint!!.browseId!! companion object } data class PlaylistItem( val info: Info?, val channel: Info?, val songCount: Int?, override val thumbnail: Thumbnail? ) : Item() { override val key get() = info!!.endpoint!!.browseId!! companion object } data class ArtistPage( val name: String?, val description: String?, val thumbnail: Thumbnail?, val shuffleEndpoint: NavigationEndpoint.Endpoint.Watch?, val radioEndpoint: NavigationEndpoint.Endpoint.Watch?, val songs: List?, val songsEndpoint: NavigationEndpoint.Endpoint.Browse?, val albums: List?, val albumsEndpoint: NavigationEndpoint.Endpoint.Browse?, val singles: List?, val singlesEndpoint: NavigationEndpoint.Endpoint.Browse?, ) data class PlaylistOrAlbumPage( val title: String?, val authors: List>?, val year: String?, val thumbnail: Thumbnail?, val url: String?, val songsPage: ItemsPage?, val otherVersions: List? ) data class NextPage( val itemsPage: ItemsPage?, val playlistId: String?, val params: String? = null, val playlistSetVideoId: String? = null ) data class RelatedPage( val songs: List? = null, val playlists: List? = null, val albums: List? = null, val artists: List? = null, ) data class ItemsPage( val items: List?, val continuation: String? ) }