migrate more Crunchyroll API endpoints to v2
This commit is contained in:
parent
0662d656ac
commit
59a457430e
|
@ -58,7 +58,7 @@ object Crunchyroll {
|
|||
|
||||
private lateinit var token: Token
|
||||
private var tokenValidUntil: Long = 0
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
|
||||
private val tokenRefreshContext = newSingleThreadContext("TokenRefreshContext")
|
||||
|
||||
private var accountID = ""
|
||||
|
@ -260,26 +260,30 @@ object Crunchyroll {
|
|||
/**
|
||||
* Browse the media available on crunchyroll.
|
||||
*
|
||||
* TODO migrate to v2
|
||||
*
|
||||
* @param sortBy
|
||||
* @param n Number of items to return, defaults to 10
|
||||
*
|
||||
* @param start start of the item list, used for pagination, default = 0
|
||||
* @param n number of items to return, default = 10
|
||||
* @param sortBy the sort order, see **[SortBy]**
|
||||
* @param ratings add user rating to the objects, default = false
|
||||
* @param seasonTag filter by season tag, if present
|
||||
* @param categories filter by category, if present
|
||||
* @return A **[BrowseResult]** object is returned.
|
||||
*/
|
||||
suspend fun browse(
|
||||
categories: List<Categories> = emptyList(),
|
||||
sortBy: SortBy = SortBy.ALPHABETICAL,
|
||||
seasonTag: String = "",
|
||||
start: Int = 0,
|
||||
n: Int = 10
|
||||
n: Int = 10,
|
||||
sortBy: SortBy = SortBy.ALPHABETICAL,
|
||||
ratings: Boolean = false,
|
||||
seasonTag: String = "",
|
||||
categories: List<Categories> = emptyList()
|
||||
): BrowseResult {
|
||||
val browseEndpoint = "/content/v1/browse"
|
||||
val browseEndpoint = "/content/v2/discover/browse"
|
||||
val parameters = mutableListOf(
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"sort_by" to sortBy.str,
|
||||
"start" to start,
|
||||
"n" to n
|
||||
"n" to n,
|
||||
"sort_by" to sortBy.str,
|
||||
"ratings" to ratings,
|
||||
"preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
)
|
||||
|
||||
// if a season tag is present add it to the parameters
|
||||
|
@ -304,9 +308,10 @@ object Crunchyroll {
|
|||
NoneBrowseResult
|
||||
}
|
||||
|
||||
// if the cache has more than 100 entries clear it, so it doesn't become a memory problem
|
||||
|
||||
|
||||
// if the cache has more than 10 entries clear it, so it doesn't become a memory problem
|
||||
// Note: this value is totally guessed and should be replaced by a properly researched value
|
||||
// TODO 100 is way to high as it's not the number of items but BrowseResults
|
||||
if (browsingCache.size > 10) {
|
||||
browsingCache.clear()
|
||||
}
|
||||
|
@ -322,19 +327,20 @@ object Crunchyroll {
|
|||
* Search fo a query term.
|
||||
* Note: currently this function only supports series/tv shows.
|
||||
*
|
||||
* TODO migrate to v2
|
||||
*
|
||||
* @param query The query term as String
|
||||
* @param n The maximum number of results to return, default = 10
|
||||
* @param ratings add user rating to the objects, default = false
|
||||
* @return A **[SearchResult]** object
|
||||
*/
|
||||
suspend fun search(query: String, n: Int = 10): SearchResult {
|
||||
val searchEndpoint = "/content/v1/search"
|
||||
suspend fun search(query: String, n: Int = 10, ratings: Boolean = false): SearchResult {
|
||||
val searchEndpoint = "/content/v2/discover/search"
|
||||
val parameters = listOf(
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"q" to query,
|
||||
"n" to n,
|
||||
"type" to "series"
|
||||
"type" to "series",
|
||||
"ratings" to ratings,
|
||||
"preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag()
|
||||
)
|
||||
|
||||
// TODO episodes have thumbnails as image, and not poster_tall/poster_tall,
|
||||
|
@ -353,10 +359,10 @@ object Crunchyroll {
|
|||
* Note: episode objects are currently not supported
|
||||
*
|
||||
* @param objects The object IDs as list of Strings
|
||||
* @param ratings the user rating of the object
|
||||
* @param ratings add user rating to the objects
|
||||
* @return A **[Collection]** of Panels
|
||||
*/
|
||||
suspend fun objects(objects: List<String>, ratings: Boolean = false): Collection2<Item> {
|
||||
suspend fun objects(objects: List<String>, ratings: Boolean = false): CollectionV2<Item> {
|
||||
val episodesEndpoint = "/content/v2/cms/objects/${objects.joinToString(",")}"
|
||||
val parameters = listOf(
|
||||
"ratings" to ratings,
|
||||
|
@ -368,7 +374,7 @@ object Crunchyroll {
|
|||
requestGet(episodesEndpoint, parameters)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Exception in objects().", ex)
|
||||
NoneCollection2
|
||||
NoneCollectionV2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,7 +515,7 @@ object Crunchyroll {
|
|||
)
|
||||
|
||||
return try {
|
||||
(requestGet(watchlistSeriesEndpoint, parameters) as Collection2<IsWatchlistItem>)
|
||||
(requestGet(watchlistSeriesEndpoint, parameters) as CollectionV2<IsWatchlistItem>)
|
||||
.total == 1
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Exception in isWatchlist() with seriesId $seriesId", ex)
|
||||
|
@ -631,14 +637,16 @@ object Crunchyroll {
|
|||
*
|
||||
* @param seriesId The crunchyroll series id of the media
|
||||
* @param n The maximum number of results to return, default = 10
|
||||
* @param ratings add user rating to the objects
|
||||
* @return A **[SimilarToResult]** object
|
||||
*/
|
||||
suspend fun similarTo(seriesId: String, n: Int = 10): SimilarToResult {
|
||||
val similarToEndpoint = "/content/v1/$accountID/similar_to"
|
||||
suspend fun similarTo(seriesId: String, n: Int = 10, ratings: Boolean = false): SimilarToResult {
|
||||
val similarToEndpoint = "/content/v2/discover/$accountID/similar_to/$seriesId"
|
||||
val parameters = listOf(
|
||||
"guid" to seriesId,
|
||||
"n" to n,
|
||||
"ratings" to ratings,
|
||||
"preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"n" to n
|
||||
)
|
||||
|
||||
return try {
|
||||
|
@ -659,7 +667,7 @@ object Crunchyroll {
|
|||
* @param n Number of items to return, defaults to 20.
|
||||
* @return A **[Collection]** containing up to n **[Item]**.
|
||||
*/
|
||||
suspend fun watchlist(n: Int = 20): Collection2<Item> {
|
||||
suspend fun watchlist(n: Int = 20): CollectionV2<Item> {
|
||||
val watchlistEndpoint = "/content/v2/discover/$accountID/watchlist"
|
||||
val parameters = listOf(
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
|
@ -681,10 +689,10 @@ object Crunchyroll {
|
|||
/**
|
||||
* List the next up episodes for the logged in account.
|
||||
*
|
||||
* @param n Number of items to return, defaults to 20.
|
||||
* @param n Number of items to return, default = 20
|
||||
* @return A **[HistoryList]** containing up to n **[UpNextAccountItem]**.
|
||||
*/
|
||||
suspend fun upNextAccount(n: Int = 20): HistoryList {
|
||||
suspend fun upNextAccount(n: Int = 10): HistoryList {
|
||||
val watchlistEndpoint = "/content/v2/discover/$accountID/history"
|
||||
val parameters = listOf(
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
|
@ -699,13 +707,21 @@ object Crunchyroll {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun recommendations(n: Int = 20, start: Int = 0): RecommendationsList {
|
||||
val recommendationsEndpoint = "/content/v1/$accountID/recommendations"
|
||||
/**
|
||||
* Returns a collection of recommendations for the currently logged in account.
|
||||
*
|
||||
* @param start start of the item list, used for pagination, default = 0
|
||||
* @param n number of items to return, default = 10
|
||||
* @param ratings add user rating to the objects, default = false
|
||||
* @return A **[RecommendationsList]** containing up to n **[Item]**.
|
||||
*/
|
||||
suspend fun recommendations(start: Int = 0, n: Int = 10, ratings: Boolean = false): RecommendationsList {
|
||||
val recommendationsEndpoint = "/content/v2/discover/$accountID/recommendations"
|
||||
val parameters = listOf(
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
"n" to n,
|
||||
"start" to start,
|
||||
"variant_id" to 0
|
||||
"n" to n,
|
||||
"ratings" to ratings,
|
||||
"locale" to Preferences.preferredSubtitleLocale.toLanguageTag(),
|
||||
)
|
||||
|
||||
return try {
|
||||
|
|
|
@ -24,7 +24,7 @@ package org.mosad.teapod.parser.crunchyroll
|
|||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
val supportedLocals = listOf(
|
||||
Locale.forLanguageTag("ar-SA"),
|
||||
|
@ -44,6 +44,10 @@ val supportedLocals = listOf(
|
|||
* 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"),
|
||||
|
@ -112,23 +116,22 @@ val NoneAccount = Account("", "", false, "")
|
|||
*/
|
||||
|
||||
@Serializable
|
||||
data class Collection<T>(
|
||||
data class CollectionV1<T>(
|
||||
@SerialName("total") val total: Int,
|
||||
@SerialName("items") val items: List<T>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Collection2<T>(
|
||||
data class CollectionV2<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>
|
||||
typealias SearchResult = CollectionV2<SearchTypedList<Item>>
|
||||
typealias BrowseResult = CollectionV2<Item>
|
||||
typealias SimilarToResult = CollectionV2<Item>
|
||||
typealias RecommendationsList = CollectionV2<Item>
|
||||
typealias Benefits = CollectionV1<Benefit>
|
||||
|
||||
/**
|
||||
* panel data classes
|
||||
|
@ -159,9 +162,9 @@ data class Poster(val height: Int, val width: Int, val source: String, val type:
|
|||
* up next & watchlist data classes
|
||||
*/
|
||||
|
||||
typealias Watchlist = Collection2<WatchlistItem>
|
||||
typealias HistoryList = Collection2<UpNextAccountItem>
|
||||
typealias UpNextSeriesList = Collection2<UpNextSeriesItem>
|
||||
typealias Watchlist = CollectionV2<WatchlistItem>
|
||||
typealias HistoryList = CollectionV2<UpNextAccountItem>
|
||||
typealias UpNextSeriesList = CollectionV2<UpNextSeriesItem>
|
||||
|
||||
@Serializable
|
||||
data class WatchlistItem(
|
||||
|
@ -221,8 +224,7 @@ data class EpisodeMetadata(
|
|||
@SerialName("series_title") val seriesTitle: String,
|
||||
)
|
||||
|
||||
val NoneCollection = Collection<Item>(0, emptyList())
|
||||
val NoneCollection2 = Collection2<Item>(0, emptyList())
|
||||
val NoneCollectionV2 = CollectionV2<Item>(0, emptyList())
|
||||
val NoneSearchResult = SearchResult(0, emptyList())
|
||||
val NoneBrowseResult = BrowseResult(0, emptyList())
|
||||
val NoneSimilarToResult = SimilarToResult(0, emptyList())
|
||||
|
@ -236,7 +238,7 @@ val NoneBenefits = Benefits(0, emptyList())
|
|||
* series data class
|
||||
*/
|
||||
|
||||
typealias Series = Collection2<SeriesItem>
|
||||
typealias Series = CollectionV2<SeriesItem>
|
||||
|
||||
@Serializable
|
||||
data class SeriesItem(
|
||||
|
@ -354,7 +356,7 @@ val NoneVersion = Version(
|
|||
variant = ""
|
||||
)
|
||||
|
||||
typealias Playheads = Collection2<PlayheadObject>
|
||||
typealias Playheads = CollectionV2<PlayheadObject>
|
||||
|
||||
@Serializable
|
||||
data class PlayheadObject(
|
||||
|
@ -450,7 +452,18 @@ 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<T>(
|
||||
@SerialName("type") val type: String,
|
||||
@SerialName("count") val count: Int,
|
||||
@SerialName("items") val items: List<T>
|
||||
)
|
||||
|
|
|
@ -66,16 +66,16 @@ class HomeViewModel : ViewModel() {
|
|||
uiState.emit(UiState.Loading)
|
||||
try {
|
||||
// run the loading in parallel to speed up the process
|
||||
val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().data }
|
||||
val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount(n = 20).data }
|
||||
val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(WATCHLIST_LENGTH).data }
|
||||
val recommendationsJob = viewModelScope.async {
|
||||
Crunchyroll.recommendations(20).items
|
||||
Crunchyroll.recommendations(n = 20).data
|
||||
}
|
||||
val recentlyAddedJob = viewModelScope.async {
|
||||
Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50).items
|
||||
Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50).data
|
||||
}
|
||||
val topTenJob = viewModelScope.async {
|
||||
Crunchyroll.browse(sortBy = SortBy.POPULARITY, n = 10).items
|
||||
Crunchyroll.browse(sortBy = SortBy.POPULARITY, n = 10).data
|
||||
}
|
||||
|
||||
val recentlyAddedItems = recentlyAddedJob.await()
|
||||
|
@ -133,7 +133,7 @@ class HomeViewModel : ViewModel() {
|
|||
viewModelScope.launch {
|
||||
uiState.update { currentUiState ->
|
||||
if (currentUiState is UiState.Normal) {
|
||||
val upNextItems = Crunchyroll.upNextAccount().data
|
||||
val upNextItems = Crunchyroll.upNextAccount(n = 20).data
|
||||
currentUiState.copy(upNextItems = upNextItems)
|
||||
} else {
|
||||
currentUiState
|
||||
|
|
|
@ -90,7 +90,7 @@ class LibraryFragmentViewModel : ViewModel() {
|
|||
delay(250)
|
||||
|
||||
val results = Crunchyroll.search(query, 50)
|
||||
.items.firstOrNull()?.items?.toItemMediaList()
|
||||
.data.firstOrNull()?.items?.toItemMediaList()
|
||||
?: listOf()
|
||||
uiState.emit(UiState.Search(results))
|
||||
}
|
||||
|
|
|
@ -9,12 +9,11 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.mosad.teapod.R
|
||||
import org.mosad.teapod.parser.crunchyroll.Collection
|
||||
import org.mosad.teapod.parser.crunchyroll.Collection2
|
||||
import org.mosad.teapod.parser.crunchyroll.CollectionV2
|
||||
import org.mosad.teapod.parser.crunchyroll.Item
|
||||
import org.mosad.teapod.parser.crunchyroll.PlayheadObject
|
||||
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Create a Intent for PlayerActivity with season and episode id.
|
||||
|
@ -36,13 +35,7 @@ fun <T> concatenate(vararg lists: List<T>): List<T> {
|
|||
}
|
||||
|
||||
// TODO move to correct location
|
||||
fun Collection<Item>.toItemMediaList(): List<ItemMedia> {
|
||||
return this.items.map {
|
||||
ItemMedia(it.id, it.title, it.images.poster_wide[0][0].source)
|
||||
}
|
||||
}
|
||||
|
||||
fun Collection2<Item>.toItemMediaList(): List<ItemMedia> {
|
||||
fun CollectionV2<Item>.toItemMediaList(): List<ItemMedia> {
|
||||
return this.data.map {
|
||||
ItemMedia(it.id, it.title, it.images.poster_wide[0][0].source)
|
||||
}
|
||||
|
@ -65,7 +58,7 @@ fun Locale.toDisplayString(fallback: String): String {
|
|||
}
|
||||
}
|
||||
|
||||
fun Collection2<PlayheadObject>.toPlayheadsMap(): Map<String, PlayheadObject> {
|
||||
fun CollectionV2<PlayheadObject>.toPlayheadsMap(): Map<String, PlayheadObject> {
|
||||
return this.data.associateBy { it.contentId }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue