2022-03-05 20:41:39 +01:00
/ * *
* 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 .
*
* /
2021-12-04 19:55:26 +01:00
package org.mosad.teapod.parser.crunchyroll
2021-12-05 00:42:56 +01:00
import android.util.Log
2022-03-04 20:29:37 +01:00
import io.ktor.client.*
2022-03-05 20:41:39 +01:00
import io.ktor.client.call.*
2022-03-05 19:22:47 +01:00
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
2022-03-04 20:29:37 +01:00
import io.ktor.client.request.*
2022-03-05 20:41:39 +01:00
import io.ktor.client.request.forms.*
2022-03-04 20:29:37 +01:00
import io.ktor.client.statement.*
import io.ktor.http.*
2021-12-04 19:55:26 +01:00
import kotlinx.coroutines.*
2022-03-05 19:22:47 +01:00
import kotlinx.serialization.SerializationException
2021-12-04 19:55:26 +01:00
import kotlinx.serialization.json.Json
2022-03-05 19:22:47 +01:00
import kotlinx.serialization.json.JsonObject
2022-01-02 22:39:31 +01:00
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
2022-01-30 12:22:25 +01:00
import org.mosad.teapod.preferences.EncryptedPreferences
2021-12-28 20:32:44 +01:00
import org.mosad.teapod.preferences.Preferences
2022-01-05 01:28:39 +01:00
import org.mosad.teapod.util.concatenate
2021-12-04 19:55:26 +01:00
private val json = Json { ignoreUnknownKeys = true }
2021-12-05 00:42:56 +01:00
object Crunchyroll {
2022-03-04 20:29:37 +01:00
private val TAG = javaClass . name
2021-12-04 19:55:26 +01:00
2022-03-05 19:22:47 +01:00
private val client = HttpClient {
install ( JsonFeature ) {
serializer = KotlinxSerializer ( json )
}
}
2021-12-20 22:14:58 +01:00
private const val baseUrl = " https://beta-api.crunchyroll.com "
2022-03-19 20:14:16 +01:00
private const val basicApiTokenUrl = " https://gitlab.com/-/snippets/2274956/raw/main/snippetfile1.txt "
private var basicApiToken : String = " "
2021-12-04 19:55:26 +01:00
2022-03-05 20:41:39 +01:00
private lateinit var token : Token
2022-01-30 12:22:25 +01:00
private var tokenValidUntil : Long = 0
2021-12-04 19:55:26 +01:00
2022-01-02 22:39:31 +01:00
private var accountID = " "
2021-12-05 01:34:06 +01:00
private var policy = " "
private var signature = " "
private var keyPairID = " "
2021-12-28 20:32:44 +01:00
private val browsingCache = arrayListOf < Item > ( )
2021-12-20 22:14:58 +01:00
2022-03-19 20:14:16 +01:00
/ * *
* Load the pai token , see :
* https : //git.mosad.xyz/mosad/NonePublicIssues/issues/1
*
* TODO handle empty file
* /
fun initBasicApiToken ( ) = runBlocking {
withContext ( Dispatchers . IO ) {
basicApiToken = ( client . get ( basicApiTokenUrl ) as HttpResponse ) . readText ( )
Log . i ( TAG , " basic auth token: $basicApiToken " )
}
}
2022-01-08 19:20:21 +01:00
/ * *
* Login to the crunchyroll API .
*
* @param username The Username / Email of the user to log in
* @param password The Accounts Password
*
* @return Boolean : True if login was successful , else false
* /
2021-12-04 19:55:26 +01:00
fun login ( username : String , password : String ) : Boolean = runBlocking {
val tokenEndpoint = " /auth/v1/token "
2022-03-05 20:41:39 +01:00
val formData = Parameters . build {
append ( " username " , username )
append ( " password " , password )
append ( " grant_type " , " password " )
append ( " scope " , " offline_access " )
}
2021-12-04 19:55:26 +01:00
2022-03-05 20:41:39 +01:00
var success = false // is false
2021-12-04 19:55:26 +01:00
withContext ( Dispatchers . IO ) {
2022-03-05 20:41:39 +01:00
// TODO handle exceptions
val response : HttpResponse = client . submitForm ( " $baseUrl $tokenEndpoint " , formParameters = formData ) {
2022-03-19 20:14:16 +01:00
header ( " Authorization " , " Basic $basicApiToken " )
2021-12-04 19:55:26 +01:00
}
2022-03-05 20:41:39 +01:00
token = response . receive ( )
tokenValidUntil = System . currentTimeMillis ( ) + ( token . expiresIn * 1000 )
2021-12-04 19:55:26 +01:00
2022-03-05 20:41:39 +01:00
Log . i ( TAG , " login complete with code ${response.status} " )
success = ( response . status == HttpStatusCode . OK )
2021-12-04 19:55:26 +01:00
}
2022-01-08 19:20:21 +01:00
return @runBlocking success
2021-12-04 19:55:26 +01:00
}
2022-01-30 12:22:25 +01:00
private fun refreshToken ( ) {
login ( EncryptedPreferences . login , EncryptedPreferences . password )
}
2022-01-06 18:39:23 +01:00
/ * *
* Requests : get , post , delete
* /
2022-03-05 19:22:47 +01:00
private suspend inline fun < reified T > request (
url : String ,
httpMethod : HttpMethod ,
2022-03-05 20:41:39 +01:00
params : List < Pair < String , Any ? > > = listOf ( ) ,
bodyObject : Any = Any ( )
2022-03-05 19:22:47 +01:00
) : T = coroutineScope {
if ( System . currentTimeMillis ( ) > tokenValidUntil ) refreshToken ( )
return @coroutineScope ( Dispatchers . IO ) {
val response : T = client . request ( url ) {
method = httpMethod
2022-03-05 20:41:39 +01:00
header ( " Authorization " , " ${token.tokenType} ${token.accessToken} " )
2022-03-05 19:22:47 +01:00
params . forEach {
parameter ( it . first , it . second )
}
2022-03-05 20:41:39 +01:00
// for json set body and content type
if ( bodyObject is JsonObject ) {
body = bodyObject
2022-03-05 19:22:47 +01:00
contentType ( ContentType . Application . Json )
}
}
response
}
}
private suspend inline fun < reified T > requestGet (
2021-12-20 22:14:58 +01:00
endpoint : String ,
2022-03-05 20:41:39 +01:00
params : List < Pair < String , Any ? > > = listOf ( ) ,
2021-12-20 22:14:58 +01:00
url : String = " "
2022-03-05 20:41:39 +01:00
) : T {
2022-01-30 12:22:25 +01:00
val path = url . ifEmpty { " $baseUrl $endpoint " }
2021-12-20 22:14:58 +01:00
2022-03-05 20:41:39 +01:00
return request ( path , HttpMethod . Get , params )
2021-12-04 19:55:26 +01:00
}
2022-01-02 22:39:31 +01:00
private suspend fun requestPost (
endpoint : String ,
2022-03-05 20:41:39 +01:00
params : List < Pair < String , Any ? > > = listOf ( ) ,
2022-03-05 19:22:47 +01:00
bodyObject : JsonObject
2022-03-05 20:41:39 +01:00
) {
2022-01-02 22:39:31 +01:00
val path = " $baseUrl $endpoint "
2022-03-05 20:41:39 +01:00
val response : HttpResponse = request ( path , HttpMethod . Post , params , bodyObject )
Log . i ( TAG , " Response: $response " )
2022-03-04 20:29:37 +01:00
}
private suspend fun requestPatch (
endpoint : String ,
2022-03-05 20:41:39 +01:00
params : List < Pair < String , Any ? > > = listOf ( ) ,
2022-03-05 19:22:47 +01:00
bodyObject : JsonObject
2022-03-05 20:41:39 +01:00
) {
2022-03-04 20:29:37 +01:00
val path = " $baseUrl $endpoint "
2022-03-05 20:41:39 +01:00
val response : HttpResponse = request ( path , HttpMethod . Patch , params , bodyObject )
Log . i ( TAG , " Response: $response " )
2022-01-02 22:39:31 +01:00
}
private suspend fun requestDelete (
endpoint : String ,
2022-03-05 20:41:39 +01:00
params : List < Pair < String , Any ? > > = listOf ( ) ,
2022-01-02 22:39:31 +01:00
url : String = " "
) = coroutineScope {
2022-01-30 12:22:25 +01:00
val path = url . ifEmpty { " $baseUrl $endpoint " }
2022-01-02 22:39:31 +01:00
2022-03-05 20:41:39 +01:00
val response : HttpResponse = request ( path , HttpMethod . Delete , params )
Log . i ( TAG , " Response: $response " )
2022-01-02 22:39:31 +01:00
}
/ * *
* Basic functions : index , account
* Needed for other functions to work properly !
* /
2021-12-20 22:14:58 +01:00
/ * *
* Retrieve the identifiers necessary for streaming . If the identifiers are
* retrieved , set the corresponding global var . The identifiers are valid for 24 h .
* /
suspend fun index ( ) {
val indexEndpoint = " /index/v2 "
2022-03-05 19:22:47 +01:00
val index : Index = requestGet ( indexEndpoint )
policy = index . cms . policy
signature = index . cms . signature
keyPairID = index . cms . keyPairId
2021-12-20 22:14:58 +01:00
2022-03-05 19:22:47 +01:00
Log . i ( TAG , " Policy : $policy " )
Log . i ( TAG , " Signature : $signature " )
Log . i ( TAG , " Key Pair ID : $keyPairID " )
2021-12-20 22:14:58 +01:00
}
2021-12-04 19:55:26 +01:00
2022-01-02 22:39:31 +01:00
/ * *
* Retrieve the account id and set the corresponding global var .
* The account id is needed for other calls .
*
* This must be execute on every start for teapod to work properly !
* /
suspend fun account ( ) {
val indexEndpoint = " /accounts/v1/me "
2022-03-05 19:22:47 +01:00
val account : Account = try {
requestGet ( indexEndpoint )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in account(). This is bad! " , ex )
NoneAccount
2022-01-02 22:39:31 +01:00
}
2022-03-05 19:22:47 +01:00
accountID = account . accountId
2022-01-02 22:39:31 +01:00
}
/ * *
2022-01-06 18:39:23 +01:00
* General element / media functions : browse , search , objects , season _list
2022-01-02 22:39:31 +01:00
* /
2021-12-04 19:55:26 +01:00
2022-03-05 19:22:47 +01:00
// TODO categories
2021-12-05 01:34:06 +01:00
/ * *
* Browse the media available on crunchyroll .
*
* @param sortBy
* @param n Number of items to return , defaults to 10
*
* @return A * * [ BrowseResult ] * * object is returned .
* /
2022-01-05 01:28:39 +01:00
suspend fun browse (
sortBy : SortBy = SortBy . ALPHABETICAL ,
seasonTag : String = " " ,
start : Int = 0 ,
n : Int = 10
) : BrowseResult {
2021-12-04 19:55:26 +01:00
val browseEndpoint = " /content/v1/browse "
2022-03-05 19:22:47 +01:00
val noneOptParams = listOf (
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
2022-03-05 19:22:47 +01:00
" sort_by " to sortBy . str ,
" start " to start ,
" n " to n
)
2022-01-05 01:28:39 +01:00
// if a season tag is present add it to the parameters
2022-01-06 18:39:23 +01:00
val parameters = if ( seasonTag . isNotEmpty ( ) ) {
2022-01-05 01:28:39 +01:00
concatenate ( noneOptParams , listOf ( " season_tag " to seasonTag ) )
} else {
noneOptParams
}
2021-12-04 19:55:26 +01:00
2022-03-05 19:22:47 +01:00
val browseResult : BrowseResult = try {
requestGet ( browseEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in browse(). " , ex )
NoneBrowseResult
}
2021-12-04 19:55:26 +01:00
2021-12-20 22:14:58 +01:00
// add results to cache TODO improve
browsingCache . clear ( )
browsingCache . addAll ( browseResult . items )
2021-12-04 19:55:26 +01:00
2021-12-20 22:14:58 +01:00
return browseResult
2021-12-04 19:55:26 +01:00
}
2021-12-27 22:50:29 +01:00
/ * *
* TODO
* /
suspend fun search ( query : String , n : Int = 10 ) : SearchResult {
2021-12-04 19:55:26 +01:00
val searchEndpoint = " /content/v1/search "
2022-03-05 20:41:39 +01:00
val parameters = listOf (
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
" q " to query ,
" n " to n ,
" type " to " series "
)
2021-12-04 19:55:26 +01:00
2021-12-27 22:50:29 +01:00
// TODO episodes have thumbnails as image, and not poster_tall/poster_tall,
// to work around this, for now only tv shows are supported
2021-12-04 19:55:26 +01:00
2022-03-05 19:22:47 +01:00
return try {
requestGet ( searchEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in search(), with query = \" $query \" . " , ex )
NoneSearchResult
}
2021-12-04 19:55:26 +01:00
}
2022-01-03 14:10:41 +01:00
/ * *
* Get a collection of series objects .
* Note : episode objects are currently not supported
*
* @param objects The object IDs as list of Strings
2022-01-05 01:28:39 +01:00
* @return A * * [ Collection ] * * of Panels
2022-01-03 14:10:41 +01:00
* /
2022-01-06 18:39:23 +01:00
suspend fun objects ( objects : List < String > ) : Collection < Item > {
2022-01-03 14:10:41 +01:00
val episodesEndpoint = " /cms/v2/DE/M3/crunchyroll/objects/ ${objects.joinToString(",")} "
val parameters = listOf (
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
2022-01-03 14:10:41 +01:00
" Signature " to signature ,
" Policy " to policy ,
" Key-Pair-Id " to keyPairID
)
2022-03-05 19:22:47 +01:00
return try {
requestGet ( episodesEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in objects(). " , ex )
NoneCollection
}
2022-01-03 14:10:41 +01:00
}
2022-01-06 18:39:23 +01:00
/ * *
* List all available seasons as * * [ SeasonListItem ] * * .
* /
@Suppress ( " unused " )
suspend fun seasonList ( ) : DiscSeasonList {
val seasonListEndpoint = " /content/v1/season_list "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-06 18:39:23 +01:00
2022-03-05 19:22:47 +01:00
return try {
requestGet ( seasonListEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in seasonList(). " , ex )
NoneDiscSeasonList
}
2022-01-06 18:39:23 +01:00
}
/ * *
* Main media functions : series , season , episodes , playback
* /
2021-12-05 01:34:06 +01:00
/ * *
2021-12-20 22:14:58 +01:00
* series id == crunchyroll id ?
2021-12-05 01:34:06 +01:00
* /
2021-12-20 22:14:58 +01:00
suspend fun series ( seriesId : String ) : Series {
2022-03-05 20:41:39 +01:00
val seriesEndpoint = " /cms/v2/ ${token.country} /M3/crunchyroll/series/ $seriesId "
2021-12-20 22:14:58 +01:00
val parameters = listOf (
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
2021-12-20 22:14:58 +01:00
" Signature " to signature ,
" Policy " to policy ,
" Key-Pair-Id " to keyPairID
)
2021-12-05 01:34:06 +01:00
2022-03-05 19:22:47 +01:00
return try {
requestGet ( seriesEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in series(). " , ex )
NoneSeries
}
2021-12-20 22:14:58 +01:00
}
2022-01-09 18:41:23 +01:00
/ * *
* TODO
* /
suspend fun upNextSeries ( seriesId : String ) : UpNextSeriesItem {
val upNextSeriesEndpoint = " /content/v1/up_next_series "
val parameters = listOf (
" series_id " to seriesId ,
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( )
2022-01-09 18:41:23 +01:00
)
2022-03-05 19:22:47 +01:00
return try {
requestGet ( upNextSeriesEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in upNextSeries(). " , ex )
NoneUpNextSeriesItem
}
2022-01-09 18:41:23 +01:00
}
2021-12-20 22:14:58 +01:00
suspend fun seasons ( seriesId : String ) : Seasons {
2022-03-05 20:41:39 +01:00
val seasonsEndpoint = " /cms/v2/ ${token.country} /M3/crunchyroll/seasons "
2021-12-20 22:14:58 +01:00
val parameters = listOf (
" series_id " to seriesId ,
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
2021-12-20 22:14:58 +01:00
" Signature " to signature ,
" Policy " to policy ,
" Key-Pair-Id " to keyPairID
)
2022-03-05 19:22:47 +01:00
return try {
requestGet ( seasonsEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in seasons(). " , ex )
NoneSeasons
}
2021-12-20 22:14:58 +01:00
}
suspend fun episodes ( seasonId : String ) : Episodes {
2022-03-05 20:41:39 +01:00
val episodesEndpoint = " /cms/v2/ ${token.country} /M3/crunchyroll/episodes "
2021-12-20 22:14:58 +01:00
val parameters = listOf (
" season_id " to seasonId ,
2022-03-05 20:41:39 +01:00
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
2021-12-20 22:14:58 +01:00
" Signature " to signature ,
" Policy " to policy ,
" Key-Pair-Id " to keyPairID
)
2022-03-05 19:22:47 +01:00
return try {
requestGet ( episodesEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in episodes(). " , ex )
NoneEpisodes
}
2021-12-20 22:14:58 +01:00
}
suspend fun playback ( url : String ) : Playback {
2022-03-05 19:22:47 +01:00
return try {
requestGet ( " " , url = url )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in playback(), with url = $url . " , ex )
NonePlayback
}
2021-12-05 01:34:06 +01:00
}
2022-01-02 22:39:31 +01:00
/ * *
2022-01-03 14:10:41 +01:00
* Additional media functions : watchlist ( series ) , playhead
2022-01-02 22:39:31 +01:00
* /
/ * *
* Check if a media is in the user ' s watchlist .
*
* @param seriesId The crunchyroll series id of the media to check
2022-01-05 01:28:39 +01:00
* @return * * [ Boolean ] * * : ture if it was found , else false
2022-01-02 22:39:31 +01:00
* /
suspend fun isWatchlist ( seriesId : String ) : Boolean {
2022-01-03 14:10:41 +01:00
val watchlistSeriesEndpoint = " /content/v1/watchlist/ $accountID / $seriesId "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-02 22:39:31 +01:00
2022-03-05 19:22:47 +01:00
return try {
( requestGet ( watchlistSeriesEndpoint , parameters ) as JsonObject )
. containsKey ( seriesId )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in isWatchlist() with seriesId = $seriesId " , ex )
false
}
2022-01-02 22:39:31 +01:00
}
/ * *
* Add a media to the user ' s watchlist .
*
* @param seriesId The crunchyroll series id of the media to check
* /
suspend fun postWatchlist ( seriesId : String ) {
2022-01-03 14:10:41 +01:00
val watchlistPostEndpoint = " /content/v1/watchlist/ $accountID "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-02 22:39:31 +01:00
val json = buildJsonObject {
put ( " content_id " , seriesId )
}
2022-03-05 19:22:47 +01:00
requestPost ( watchlistPostEndpoint , parameters , json )
2022-01-02 22:39:31 +01:00
}
/ * *
* Remove a media from the user ' s watchlist .
*
* @param seriesId The crunchyroll series id of the media to check
* /
suspend fun deleteWatchlist ( seriesId : String ) {
2022-01-03 14:10:41 +01:00
val watchlistDeleteEndpoint = " /content/v1/watchlist/ $accountID / $seriesId "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-02 22:39:31 +01:00
2022-01-03 14:10:41 +01:00
requestDelete ( watchlistDeleteEndpoint , parameters )
2022-01-02 22:39:31 +01:00
}
/ * *
2022-01-05 01:28:39 +01:00
* Get playhead information for all episodes in episodeIDs .
* The Information returned contains the playhead position , watched state
* and last modified date .
*
* @param episodeIDs A * * [ List ] * * of episodes IDs as strings .
* @return A * * [ Map ] * * < String , * * [ PlayheadObject ] * * > containing playback info .
2022-01-02 22:39:31 +01:00
* /
2022-01-05 01:28:39 +01:00
suspend fun playheads ( episodeIDs : List < String > ) : PlayheadsMap {
val playheadsEndpoint = " /content/v1/playheads/ $accountID / ${episodeIDs.joinToString(",")} "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-05 01:28:39 +01:00
2022-03-05 19:22:47 +01:00
return try {
requestGet ( playheadsEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in upNextSeries(). " , ex )
emptyMap ( )
}
2022-01-02 22:39:31 +01:00
}
2022-01-09 18:41:23 +01:00
suspend fun postPlayheads ( episodeId : String , playhead : Int ) {
val playheadsEndpoint = " /content/v1/playheads/ $accountID "
2022-03-05 20:41:39 +01:00
val parameters = listOf ( " locale " to Preferences . preferredLocale . toLanguageTag ( ) )
2022-01-09 18:41:23 +01:00
val json = buildJsonObject {
put ( " content_id " , episodeId )
put ( " playhead " , playhead )
}
2022-03-05 19:22:47 +01:00
requestPost ( playheadsEndpoint , parameters , json )
2022-01-09 18:41:23 +01:00
}
2022-01-03 14:10:41 +01:00
/ * *
* Listing functions : watchlist ( list ) , up _next _account
* /
2022-01-05 01:28:39 +01:00
/ * *
* List items present in the watchlist .
*
* @param n Number of items to return , defaults to 20.
* @return A * * [ Watchlist ] * * containing up to n * * [ Item ] * * .
* /
2022-01-03 14:10:41 +01:00
suspend fun watchlist ( n : Int = 20 ) : Watchlist {
val watchlistEndpoint = " /content/v1/ $accountID /watchlist "
2022-03-05 20:41:39 +01:00
val parameters = listOf (
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
" n " to n
)
2022-01-03 14:10:41 +01:00
2022-03-05 19:22:47 +01:00
val list : ContinueWatchingList = try {
requestGet ( watchlistEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in watchlist(). " , ex )
NoneContinueWatchingList
}
2022-01-03 14:10:41 +01:00
val objects = list . items . map { it . panel . episodeMetadata . seriesId }
return objects ( objects )
}
2022-01-05 00:28:49 +01:00
/ * *
2022-01-05 01:28:39 +01:00
* List the next up episodes for the logged in account .
*
* @param n Number of items to return , defaults to 20.
* @return A * * [ ContinueWatchingList ] * * containing up to n * * [ ContinueWatchingItem ] * * .
2022-01-05 00:28:49 +01:00
* /
suspend fun upNextAccount ( n : Int = 20 ) : ContinueWatchingList {
val watchlistEndpoint = " /content/v1/ $accountID /up_next_account "
2022-03-05 20:41:39 +01:00
val parameters = listOf (
" locale " to Preferences . preferredLocale . toLanguageTag ( ) ,
" n " to n
)
2022-01-05 00:28:49 +01:00
2022-03-05 19:22:47 +01:00
return try {
requestGet ( watchlistEndpoint , parameters )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in upNextAccount(). " , ex )
NoneContinueWatchingList
}
2022-01-05 00:28:49 +01:00
}
2022-02-05 20:02:33 +01:00
/ * *
* Account / Profile functions
* /
suspend fun profile ( ) : Profile {
val profileEndpoint = " /accounts/v1/me/profile "
2022-03-05 19:22:47 +01:00
return try {
requestGet ( profileEndpoint )
} catch ( ex : SerializationException ) {
Log . e ( TAG , " SerializationException in profile(). " , ex )
NoneProfile
}
2022-02-05 20:02:33 +01:00
}
2022-03-04 20:29:37 +01:00
suspend fun postPrefSubLanguage ( languageTag : String ) {
val profileEndpoint = " /accounts/v1/me/profile "
val json = buildJsonObject {
put ( " preferred_content_subtitle_language " , languageTag )
}
2022-03-05 19:22:47 +01:00
requestPatch ( profileEndpoint , bodyObject = json )
2022-03-04 20:29:37 +01:00
}
2022-01-02 17:59:23 +01:00
}