204 lines
7.0 KiB
Kotlin
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?
|
|
)
|
|
}
|