/** * Teapod * * Copyright 2020-2022 * * 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.Locale val supportedAudioLocals = listOf( Locale.forLanguageTag("ar-SA"), Locale.forLanguageTag("ca-ES"), Locale.forLanguageTag("de-DE"), Locale.forLanguageTag("en-US"), Locale.forLanguageTag("en-IN"), Locale.forLanguageTag("es-419"), Locale.forLanguageTag("es-ES"), Locale.forLanguageTag("fr-FR"), Locale.forLanguageTag("hi-IN"), Locale.forLanguageTag("it-IT"), Locale.forLanguageTag("ko-KR"), Locale.forLanguageTag("pl-PL"), Locale.forLanguageTag("pt-BR"), Locale.forLanguageTag("pt-PT"), Locale.forLanguageTag("ru-RU"), Locale.forLanguageTag("ta-IN"), Locale.forLanguageTag("th-TH"), Locale.forLanguageTag("zh-CN"), Locale.forLanguageTag("zh-TW"), Locale.ROOT ) val supportedSubtitleLocals = listOf( Locale.forLanguageTag("ar-SA"), Locale.forLanguageTag("ca-ES"), Locale.forLanguageTag("de-DE"), Locale.forLanguageTag("en-US"), Locale.forLanguageTag("es-419"), Locale.forLanguageTag("es-ES"), Locale.forLanguageTag("fr-FR"), Locale.forLanguageTag("hi-IN"), Locale.forLanguageTag("it-IT"), Locale.forLanguageTag("ms-MY"), Locale.forLanguageTag("pl-PL"), Locale.forLanguageTag("pt-BR"), Locale.forLanguageTag("pt-PT"), Locale.forLanguageTag("ru-RU"), Locale.forLanguageTag("tr-TR"), Locale.ROOT ) /** * data classes for browse * TODO make class names more clear/possibly overlapping for now */ /** * Enum of all supported sorting orders. */ 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 CollectionV1( @SerialName("total") val total: Int, @SerialName("items") val items: List ) @Serializable data class CollectionV2( @SerialName("total") val total: Int, @SerialName("data") val data: List ) typealias SearchResult = CollectionV2> typealias BrowseResult = CollectionV2 typealias SimilarToResult = CollectionV2 typealias RecommendationsList = CollectionV2 typealias Benefits = CollectionV1 /** * 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>, val poster_wide: List>) // 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 = CollectionV2 typealias HistoryList = CollectionV2 typealias UpNextSeriesList = CollectionV2 @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 NoneCollectionV2 = CollectionV2(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 = CollectionV2 @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, @SerialName("audio_locales") val audioLocales: List, @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 ) @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 ) @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? = null, @SerialName("streams_link") val streamsLink: String, ) @Serializable data class Thumbnail( @SerialName("thumbnail") val thumbnail: List> ) @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 = CollectionV2 @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, ) @Serializable data class StreamList( @SerialName("adaptive_dash") val adaptive_dash: Map, @SerialName("adaptive_hls") val adaptive_hls: Map, @SerialName("download_dash") val downloadDash: Map, @SerialName("download_hls") val download_hls: Map, // @SerialName("drm_adaptive_dash") val drmAdaptiveDash: Map, // @SerialName("drm_adaptive_hls") val drmAdaptiveHls: Map, // @SerialName("drm_download_dash") val drmDownloadDash: Map, // @SerialName("drm_download_hls") val drmDownloadHls: Map, // @SerialName("vo_adaptive_dash") val vo_adaptive_dash: Map, // @SerialName("vo_adaptive_hls") val vo_adaptive_hls: Map, // @SerialName("vo_drm_adaptive_dash") val vo_drm_adaptive_dash: Map, // @SerialName("vo_drm_adaptive_hls") val vo_drm_adaptive_hls: Map, ) @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, ) @Suppress("unused") val NoneBenefit = Benefit( benefit = "", source = "" ) /** * search result typed list data class */ @Serializable data class SearchTypedList( @SerialName("type") val type: String, @SerialName("count") val count: Int, @SerialName("items") val items: List )