Files
muza/innertube/src/main/kotlin/it/vfsfitvnm/youtubemusic/Innertube.kt
2022-10-03 15:20:41 +02:00

204 lines
7.0 KiB
Kotlin

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<T : NavigationEndpoint.Endpoint>(
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<NavigationEndpoint.Endpoint.Watch>?,
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
val album: Info<NavigationEndpoint.Endpoint.Browse>?,
val durationText: String?,
override val thumbnail: Thumbnail?
) : Item() {
override val key get() = info!!.endpoint!!.videoId!!
companion object
}
data class VideoItem(
val info: Info<NavigationEndpoint.Endpoint.Watch>?,
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
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<NavigationEndpoint.Endpoint.Browse>?,
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
val year: String?,
override val thumbnail: Thumbnail?
) : Item() {
override val key get() = info!!.endpoint!!.browseId!!
companion object
}
data class ArtistItem(
val info: Info<NavigationEndpoint.Endpoint.Browse>?,
val subscribersCountText: String?,
override val thumbnail: Thumbnail?
) : Item() {
override val key get() = info!!.endpoint!!.browseId!!
companion object
}
data class PlaylistItem(
val info: Info<NavigationEndpoint.Endpoint.Browse>?,
val channel: Info<NavigationEndpoint.Endpoint.Browse>?,
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<SongItem>?,
val songsEndpoint: NavigationEndpoint.Endpoint.Browse?,
val albums: List<AlbumItem>?,
val albumsEndpoint: NavigationEndpoint.Endpoint.Browse?,
val singles: List<AlbumItem>?,
val singlesEndpoint: NavigationEndpoint.Endpoint.Browse?,
)
data class PlaylistOrAlbumPage(
val title: String?,
val authors: List<Info<NavigationEndpoint.Endpoint.Browse>>?,
val year: String?,
val thumbnail: Thumbnail?,
val url: String?,
val songsPage: ItemsPage<SongItem>?,
val otherVersions: List<AlbumItem>?
)
data class NextPage(
val itemsPage: ItemsPage<SongItem>?,
val playlistId: String?,
val params: String? = null,
val playlistSetVideoId: String? = null
)
data class RelatedPage(
val songs: List<SongItem>? = null,
val playlists: List<PlaylistItem>? = null,
val albums: List<AlbumItem>? = null,
val artists: List<ArtistItem>? = null,
)
data class ItemsPage<T : Item>(
val items: List<T>?,
val continuation: String?
)
}