Rename youtube-music module to innertube and rewrite it
This commit is contained in:
204
innertube/src/main/kotlin/it/vfsfitvnm/youtubemusic/Innertube.kt
Normal file
204
innertube/src/main/kotlin/it/vfsfitvnm/youtubemusic/Innertube.kt
Normal file
@@ -0,0 +1,204 @@
|
||||
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)"
|
||||
|
||||
// contents.singleColumnBrowseResultsRenderer.tabs.tabRenderer.content.sectionListRenderer.contents(musicPlaylistShelfRenderer(continuations,contents.musicResponsiveListItemRenderer(flexColumns,fixedColumns,thumbnail)),gridRenderer(continuations,items.musicTwoRowItemRenderer(thumbnailRenderer,title,subtitle,navigationEndpoint)))
|
||||
|
||||
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>?
|
||||
)
|
||||
|
||||
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?
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user