457 lines
14 KiB
Kotlin
457 lines
14 KiB
Kotlin
/**
|
|
* Teapod
|
|
*
|
|
* Copyright 2020-2022 <seil0@mosad.xyz>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
package org.mosad.teapod.parser.crunchyroll
|
|
|
|
import kotlinx.serialization.SerialName
|
|
import kotlinx.serialization.Serializable
|
|
import java.util.*
|
|
|
|
val supportedLocals = listOf(
|
|
Locale.forLanguageTag("ar-SA"),
|
|
Locale.forLanguageTag("de-DE"),
|
|
Locale.forLanguageTag("en-US"),
|
|
Locale.forLanguageTag("es-419"),
|
|
Locale.forLanguageTag("es-ES"),
|
|
Locale.forLanguageTag("fr-FR"),
|
|
Locale.forLanguageTag("it-IT"),
|
|
Locale.forLanguageTag("pt-BR"),
|
|
Locale.forLanguageTag("pt-PT"),
|
|
Locale.forLanguageTag("ru-RU"),
|
|
Locale.ROOT
|
|
)
|
|
|
|
/**
|
|
* data classes for browse
|
|
* TODO make class names more clear/possibly overlapping for now
|
|
*/
|
|
enum class SortBy(val str: String) {
|
|
ALPHABETICAL("alphabetical"),
|
|
NEWLY_ADDED("newly_added"),
|
|
POPULARITY("popularity")
|
|
}
|
|
|
|
@Suppress("unused")
|
|
enum class Categories(val str: String) {
|
|
ACTION("action"),
|
|
ADVENTURE("adventure"),
|
|
COMEDY("comedy"),
|
|
DRAMA("drama"),
|
|
FANTASY("fantasy"),
|
|
MUSIC("music"),
|
|
ROMANCE("romance"),
|
|
SCI_FI("sci-fi"),
|
|
SEINEN("seinen"),
|
|
SHOJO("shojo"),
|
|
SHONEN("shonen"),
|
|
SLICE_OF_LIFE("slice+of+life"),
|
|
SPORTS("sports"),
|
|
SUPERNATURAL("supernatural"),
|
|
THRILLER("thriller")
|
|
}
|
|
|
|
/**
|
|
* token, index, account. This must pe present for the app to work!
|
|
*/
|
|
@Serializable
|
|
data class Token(
|
|
@SerialName("access_token") val accessToken: String,
|
|
@SerialName("refresh_token") val refreshToken: String,
|
|
@SerialName("expires_in") val expiresIn: Int,
|
|
@SerialName("token_type") val tokenType: String,
|
|
@SerialName("scope") val scope: String,
|
|
@SerialName("country") val country: String,
|
|
@SerialName("account_id") val accountId: String,
|
|
)
|
|
|
|
@Serializable
|
|
data class Index(
|
|
@SerialName("cms") val cms: CMS,
|
|
@SerialName("service_available") val serviceAvailable: Boolean,
|
|
)
|
|
|
|
@Serializable
|
|
data class CMS(
|
|
@SerialName("bucket") val bucket: String,
|
|
@SerialName("policy") val policy: String,
|
|
@SerialName("signature") val signature: String,
|
|
@SerialName("key_pair_id") val keyPairId: String,
|
|
@SerialName("expires") val expires: String,
|
|
)
|
|
|
|
@Serializable
|
|
data class Account(
|
|
@SerialName("account_id") val accountId: String,
|
|
@SerialName("external_id") val externalId: String,
|
|
@SerialName("email_verified") val emailVerified: Boolean,
|
|
@SerialName("created") val created: String,
|
|
)
|
|
val NoneAccount = Account("", "", false, "")
|
|
|
|
/**
|
|
* search, browse, DiscSeasonList, Watchlist, ContinueWatchingList data types all use Collection
|
|
*/
|
|
|
|
@Serializable
|
|
data class Collection<T>(
|
|
@SerialName("total") val total: Int,
|
|
@SerialName("items") val items: List<T>
|
|
)
|
|
|
|
@Serializable
|
|
data class Collection2<T>(
|
|
@SerialName("total") val total: Int,
|
|
@SerialName("data") val data: List<T>
|
|
)
|
|
|
|
typealias SearchResult = Collection<SearchCollection>
|
|
typealias SearchCollection = Collection<Item>
|
|
typealias BrowseResult = Collection<Item>
|
|
typealias SimilarToResult = Collection<Item>
|
|
typealias RecommendationsList = Collection<Item>
|
|
typealias Benefits = Collection<Benefit>
|
|
|
|
/**
|
|
* panel data classes
|
|
*/
|
|
|
|
// the data class Item is used in browse, search, watchlist and similar to
|
|
// TODO rename to MediaPanel
|
|
@Serializable
|
|
data class Item(
|
|
val id: String,
|
|
val title: String,
|
|
val type: String,
|
|
val channel_id: String,
|
|
val description: String,
|
|
val images: Images
|
|
// TODO series_metadata etc.
|
|
// TODO add slug_title if present in search, browse, similar to
|
|
)
|
|
|
|
@Serializable
|
|
data class Images(val poster_tall: List<List<Poster>>, val poster_wide: List<List<Poster>>)
|
|
// crunchyroll why?
|
|
|
|
@Serializable
|
|
data class Poster(val height: Int, val width: Int, val source: String, val type: String)
|
|
|
|
/**
|
|
* up next & watchlist data classes
|
|
*/
|
|
|
|
typealias Watchlist = Collection2<WatchlistItem>
|
|
typealias HistoryList = Collection2<UpNextAccountItem>
|
|
typealias UpNextSeriesList = Collection2<UpNextSeriesItem>
|
|
|
|
@Serializable
|
|
data class WatchlistItem(
|
|
@SerialName("panel") val panel: EpisodePanel,
|
|
@SerialName("new") val new: Boolean,
|
|
@SerialName("playhead") val playhead: Int,
|
|
@SerialName("fully_watched") val fullyWatched: Boolean = false,
|
|
@SerialName("never_watched") val neverWatched: Boolean = false,
|
|
@SerialName("is_favorite") val isFavorite: Boolean,
|
|
)
|
|
|
|
@Serializable
|
|
data class IsWatchlistItem(
|
|
@SerialName("id") val id: String,
|
|
@SerialName("is_favorite") val isFavorite: Boolean,
|
|
@SerialName("date_added") val dateAdded: String
|
|
)
|
|
|
|
@Serializable
|
|
data class UpNextAccountItem(
|
|
@SerialName("panel") val panel: EpisodePanel,
|
|
@SerialName("new") val new: Boolean,
|
|
@SerialName("playhead") val playhead: Int,
|
|
@SerialName("fully_watched") val fullyWatched: Boolean = false,
|
|
)
|
|
|
|
@Serializable
|
|
data class UpNextSeriesItem(
|
|
@SerialName("panel") val panel: EpisodePanel,
|
|
@SerialName("playhead") val playhead: Int,
|
|
@SerialName("fully_watched") val fullyWatched: Boolean,
|
|
@SerialName("never_watched") val neverWatched: Boolean,
|
|
|
|
)
|
|
|
|
// EpisodePanel is used in ContinueWatchingItem and UpNextSeriesItem
|
|
@Serializable
|
|
data class EpisodePanel(
|
|
@SerialName("id") val id: String,
|
|
@SerialName("title") val title: String,
|
|
@SerialName("type") val type: String,
|
|
@SerialName("channel_id") val channelId: String,
|
|
@SerialName("description") val description: String,
|
|
@SerialName("episode_metadata") val episodeMetadata: EpisodeMetadata,
|
|
@SerialName("images") val images: Thumbnail,
|
|
// @SerialName("streams_link") val streamsLink: String,
|
|
)
|
|
|
|
@Serializable
|
|
data class EpisodeMetadata(
|
|
@SerialName("duration_ms") val durationMs: Int,
|
|
@SerialName("episode_number") val episodeNumber: Int? = null, // default/nullable value since optional
|
|
@SerialName("season_id") val seasonId: String,
|
|
@SerialName("season_number") val seasonNumber: Int,
|
|
@SerialName("season_title") val seasonTitle: String,
|
|
@SerialName("series_id") val seriesId: String,
|
|
@SerialName("series_title") val seriesTitle: String,
|
|
)
|
|
|
|
val NoneCollection = Collection<Item>(0, emptyList())
|
|
val NoneCollection2 = Collection2<Item>(0, emptyList())
|
|
val NoneSearchResult = SearchResult(0, emptyList())
|
|
val NoneBrowseResult = BrowseResult(0, emptyList())
|
|
val NoneSimilarToResult = SimilarToResult(0, emptyList())
|
|
val NoneWatchlist = Watchlist(0, emptyList())
|
|
val NoneHistoryList = HistoryList(0, emptyList())
|
|
val NoneUpNextSeriesList = UpNextSeriesList(0, emptyList())
|
|
val NoneRecommendationsList = RecommendationsList(0, emptyList())
|
|
val NoneBenefits = Benefits(0, emptyList())
|
|
|
|
/**
|
|
* series data class
|
|
*/
|
|
|
|
typealias Series = Collection2<SeriesItem>
|
|
|
|
@Serializable
|
|
data class SeriesItem(
|
|
@SerialName("id") val id: String,
|
|
@SerialName("title") val title: String,
|
|
@SerialName("description") val description: String,
|
|
@SerialName("images") val images: Images,
|
|
@SerialName("is_simulcast") val isSimulcast: Boolean,
|
|
@SerialName("maturity_ratings") val maturityRatings: List<String>,
|
|
@SerialName("audio_locales") val audioLocales: List<String>,
|
|
@SerialName("episode_count") val episodeCount: Int
|
|
)
|
|
|
|
val NoneSeriesItem = SeriesItem("", "", "", Images(emptyList(), emptyList()), false, emptyList(), emptyList(), 0)
|
|
val NoneSeries = Series(1, listOf(NoneSeriesItem))
|
|
|
|
/**
|
|
* Seasons data classes
|
|
*/
|
|
@Serializable
|
|
data class Seasons(
|
|
@SerialName("total") val total: Int,
|
|
@SerialName("data") val data: List<Season>
|
|
)
|
|
|
|
@Serializable
|
|
data class Season(
|
|
@SerialName("id") val id: String,
|
|
@SerialName("title") val title: String,
|
|
@SerialName("slug_title") val slugTitle: String,
|
|
@SerialName("series_id") val seriesId: String,
|
|
@SerialName("season_number") val seasonNumber: Int,
|
|
@SerialName("is_subbed") val isSubbed: Boolean,
|
|
@SerialName("is_dubbed") val isDubbed: Boolean,
|
|
)
|
|
|
|
val NoneSeasons = Seasons(0, emptyList())
|
|
val NoneSeason = Season("", "", "", "", 0, isSubbed = false, isDubbed = false)
|
|
|
|
|
|
/**
|
|
* Episodes data classes
|
|
*/
|
|
@Serializable
|
|
data class Episodes(
|
|
@SerialName("total") val total: Int,
|
|
@SerialName("data") val data: List<Episode>
|
|
)
|
|
|
|
@Serializable
|
|
data class Episode(
|
|
@SerialName("id") val id: String,
|
|
@SerialName("title") val title: String,
|
|
@SerialName("series_id") val seriesId: String,
|
|
@SerialName("season_title") val seasonTitle: String,
|
|
@SerialName("season_id") val seasonId: String,
|
|
@SerialName("season_number") val seasonNumber: Int,
|
|
@SerialName("episode") val episode: String,
|
|
@SerialName("episode_number") val episodeNumber: Int? = null,
|
|
@SerialName("description") val description: String,
|
|
@SerialName("next_episode_id") val nextEpisodeId: String? = null, // default/nullable value since optional
|
|
@SerialName("next_episode_title") val nextEpisodeTitle: String? = null, // default/nullable value since optional
|
|
@SerialName("is_subbed") val isSubbed: Boolean,
|
|
@SerialName("is_dubbed") val isDubbed: Boolean,
|
|
@SerialName("images") val images: Thumbnail,
|
|
@SerialName("duration_ms") val durationMs: Int,
|
|
@SerialName("versions") val versions: List<Version>? = null,
|
|
@SerialName("streams_link") val streamsLink: String,
|
|
)
|
|
|
|
@Serializable
|
|
data class Thumbnail(
|
|
@SerialName("thumbnail") val thumbnail: List<List<Poster>>
|
|
)
|
|
|
|
@Serializable
|
|
data class Version(
|
|
@SerialName("audio_locale") val audioLocale: String,
|
|
@SerialName("guid") val guid: String,
|
|
@SerialName("is_premium_only") val isPremiumOnly: Boolean,
|
|
@SerialName("media_guid") val mediaGUID: String,
|
|
@SerialName("original") val original: Boolean,
|
|
@SerialName("season_guid") val seasonGUID: String,
|
|
@SerialName("variant") val variant: String,
|
|
)
|
|
|
|
val NoneEpisodes = Episodes(0, listOf())
|
|
val NoneEpisode = Episode(
|
|
id = "",
|
|
title = "",
|
|
seriesId = "",
|
|
seasonId = "",
|
|
seasonTitle = "",
|
|
seasonNumber = 0,
|
|
episode = "",
|
|
episodeNumber = 0,
|
|
description = "",
|
|
nextEpisodeId = "",
|
|
nextEpisodeTitle = "",
|
|
isSubbed = false,
|
|
isDubbed = false,
|
|
images = Thumbnail(listOf()),
|
|
durationMs = 0,
|
|
versions = emptyList(),
|
|
streamsLink = ""
|
|
)
|
|
|
|
val NoneVersion = Version(
|
|
audioLocale = "",
|
|
guid = "",
|
|
isPremiumOnly = false,
|
|
mediaGUID = "",
|
|
original = true,
|
|
seasonGUID = "",
|
|
variant = ""
|
|
)
|
|
|
|
typealias Playheads = Collection2<PlayheadObject>
|
|
|
|
@Serializable
|
|
data class PlayheadObject(
|
|
@SerialName("playhead") val playhead: Int,
|
|
@SerialName("content_id") val contentId: String,
|
|
@SerialName("fully_watched") val fullyWatched: Boolean,
|
|
@SerialName("last_modified") val lastModified: String,
|
|
)
|
|
|
|
val NonePlayheads = Playheads(0, emptyList())
|
|
|
|
/**
|
|
* Meta data for a episode intro. All time values are in seconds.
|
|
*/
|
|
@Serializable
|
|
data class DatalabIntro(
|
|
@SerialName("media_id") val mediaId: String,
|
|
@SerialName("startTime") val startTime: Float,
|
|
@SerialName("endTime") val endTime: Float,
|
|
@SerialName("duration") val duration: Float,
|
|
@SerialName("comparedWith") val comparedWith: String,
|
|
@SerialName("ordering") val ordering: String,
|
|
@SerialName("last_updated") val lastUpdated: String,
|
|
)
|
|
|
|
val NoneDatalabIntro = DatalabIntro("", 0f, 0f, 0f, "", "", "")
|
|
|
|
/**
|
|
* playback/stream data classes
|
|
*/
|
|
@Serializable
|
|
data class Streams(
|
|
@SerialName("total") val total: Int,
|
|
@SerialName("data") val data: List<StreamList>,
|
|
)
|
|
|
|
@Serializable
|
|
data class StreamList(
|
|
@SerialName("adaptive_dash") val adaptive_dash: Map<String, Stream>,
|
|
@SerialName("adaptive_hls") val adaptive_hls: Map<String, Stream>,
|
|
@SerialName("download_dash") val downloadDash: Map<String, Stream>,
|
|
@SerialName("download_hls") val download_hls: Map<String, Stream>,
|
|
// @SerialName("drm_adaptive_dash") val drmAdaptiveDash: Map<String, Stream>,
|
|
// @SerialName("drm_adaptive_hls") val drmAdaptiveHls: Map<String, Stream>,
|
|
// @SerialName("drm_download_dash") val drmDownloadDash: Map<String, Stream>,
|
|
// @SerialName("drm_download_hls") val drmDownloadHls: Map<String, Stream>,
|
|
// @SerialName("vo_adaptive_dash") val vo_adaptive_dash: Map<String, Stream>,
|
|
// @SerialName("vo_adaptive_hls") val vo_adaptive_hls: Map<String, Stream>,
|
|
// @SerialName("vo_drm_adaptive_dash") val vo_drm_adaptive_dash: Map<String, Stream>,
|
|
// @SerialName("vo_drm_adaptive_hls") val vo_drm_adaptive_hls: Map<String, Stream>,
|
|
)
|
|
|
|
@Serializable
|
|
data class Stream(
|
|
@SerialName("hardsub_locale") val hardsubLocale: String = "", // default/nullable value since might be optional
|
|
@SerialName("url") val url: String = "", // default/nullable value since optional
|
|
@SerialName("vcodec") val vcodec: String = "", // default/nullable value since optional
|
|
)
|
|
|
|
val NoneStreams = Streams(
|
|
0,
|
|
arrayListOf(StreamList(
|
|
mapOf(), mapOf(), mapOf(), mapOf()
|
|
))
|
|
)
|
|
|
|
/**
|
|
* profile data class
|
|
*/
|
|
@Serializable
|
|
data class Profile(
|
|
@SerialName("avatar") val avatar: String,
|
|
@SerialName("email") val email: String,
|
|
@SerialName("maturity_rating") val maturityRating: String,
|
|
@SerialName("preferred_content_audio_language") val preferredContentAudioLanguage: String,
|
|
@SerialName("preferred_content_subtitle_language") val preferredContentSubtitleLanguage: String,
|
|
@SerialName("username") val username: String,
|
|
)
|
|
val NoneProfile = Profile(
|
|
avatar = "",
|
|
email = "",
|
|
maturityRating = "",
|
|
preferredContentAudioLanguage = "",
|
|
preferredContentSubtitleLanguage = "",
|
|
username = ""
|
|
)
|
|
|
|
/**
|
|
* benefit data class
|
|
*/
|
|
@Serializable
|
|
data class Benefit(
|
|
@SerialName("benefit") val benefit: String,
|
|
@SerialName("source") val source: String,
|
|
)
|
|
val NoneBenefit = Benefit(
|
|
benefit = "",
|
|
source = ""
|
|
)
|