feat(all/jellyfin): Add support for collections (#3217)
This commit is contained in:
parent
26e09c144f
commit
a59119a6c7
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Jellyfin'
|
extName = 'Jellyfin'
|
||||||
extClass = '.JellyfinFactory'
|
extClass = '.JellyfinFactory'
|
||||||
extVersionCode = 13
|
extVersionCode = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -142,7 +142,7 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
addQueryParameter("Recursive", "true")
|
addQueryParameter("Recursive", "true")
|
||||||
addQueryParameter("SortBy", "SortName")
|
addQueryParameter("SortBy", "SortName")
|
||||||
addQueryParameter("SortOrder", "Ascending")
|
addQueryParameter("SortOrder", "Ascending")
|
||||||
addQueryParameter("IncludeItemTypes", "Movie,Season")
|
addQueryParameter("IncludeItemTypes", "Movie,Season,BoxSet")
|
||||||
addQueryParameter("ImageTypeLimit", "1")
|
addQueryParameter("ImageTypeLimit", "1")
|
||||||
addQueryParameter("ParentId", parentId)
|
addQueryParameter("ParentId", parentId)
|
||||||
addQueryParameter("EnableImageTypes", "Primary")
|
addQueryParameter("EnableImageTypes", "Primary")
|
||||||
@ -152,9 +152,22 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
|
val splitCollections = preferences.getSplitCol
|
||||||
val page = response.request.url.queryParameter("StartIndex")!!.toInt() / SEASONS_LIMIT + 1
|
val page = response.request.url.queryParameter("StartIndex")!!.toInt() / SEASONS_LIMIT + 1
|
||||||
val data = response.parseAs<ItemsDto>()
|
val data = response.parseAs<ItemsDto>()
|
||||||
val animeList = data.items.map { it.toSAnime(baseUrl, userId!!, apiKey!!) }
|
val animeList = data.items.flatMap {
|
||||||
|
if (it.type == "BoxSet" && splitCollections) {
|
||||||
|
val url = popularAnimeRequest(page).url.newBuilder().apply {
|
||||||
|
setQueryParameter("ParentId", it.id)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
popularAnimeParse(
|
||||||
|
client.newCall(GET(url)).execute(),
|
||||||
|
).animes
|
||||||
|
} else {
|
||||||
|
listOf(it.toSAnime(baseUrl, userId!!, apiKey!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
return AnimesPage(animeList, SEASONS_LIMIT * page < data.itemCount)
|
return AnimesPage(animeList, SEASONS_LIMIT * page < data.itemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +271,26 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
}.build()
|
}.build()
|
||||||
} else if (fragment.startsWith("movie")) {
|
} else if (fragment.startsWith("movie")) {
|
||||||
httpUrl.newBuilder().fragment(null).build()
|
httpUrl.newBuilder().fragment(null).build()
|
||||||
|
} else if (fragment.startsWith("boxSet")) {
|
||||||
|
val itemId = httpUrl.pathSegments[3]
|
||||||
|
httpUrl.newBuilder().apply {
|
||||||
|
removePathSegment(3)
|
||||||
|
addQueryParameter("Recursive", "true")
|
||||||
|
addQueryParameter("SortBy", "SortName")
|
||||||
|
addQueryParameter("SortOrder", "Ascending")
|
||||||
|
addQueryParameter("IncludeItemTypes", "Movie,Season,BoxSet,Series")
|
||||||
|
addQueryParameter("ParentId", itemId)
|
||||||
|
}.build()
|
||||||
|
} else if (fragment.startsWith("series")) {
|
||||||
|
val itemId = httpUrl.pathSegments[3]
|
||||||
|
httpUrl.newBuilder().apply {
|
||||||
|
encodedPath("/")
|
||||||
|
encodedQuery(null)
|
||||||
|
addPathSegment("Shows")
|
||||||
|
addPathSegment(itemId)
|
||||||
|
addPathSegment("Episodes")
|
||||||
|
addQueryParameter("api_key", apiKey)
|
||||||
|
}.build()
|
||||||
} else {
|
} else {
|
||||||
httpUrl
|
httpUrl
|
||||||
}
|
}
|
||||||
@ -266,19 +299,48 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
val epDetails = preferences.getEpDetails
|
val httpUrl = response.request.url
|
||||||
|
val episodeList = if (httpUrl.fragment == "boxSet") {
|
||||||
|
val data = response.parseAs<ItemsDto>()
|
||||||
|
val animeList = data.items.map {
|
||||||
|
it.toSAnime(baseUrl, userId!!, apiKey!!)
|
||||||
|
}.sortedByDescending { it.title }
|
||||||
|
animeList.flatMap {
|
||||||
|
client.newCall(episodeListRequest(it))
|
||||||
|
.execute()
|
||||||
|
.let { res ->
|
||||||
|
episodeListParse(res, "${it.title} - ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
episodeListParse(response, "")
|
||||||
|
}
|
||||||
|
|
||||||
val episodeList = if (response.request.url.toString().startsWith("$baseUrl/Users/")) {
|
return if (preferences.sortEp) {
|
||||||
|
episodeList.sortedByDescending { it.date_upload }
|
||||||
|
} else {
|
||||||
|
episodeList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun episodeListParse(response: Response, prefix: String): List<SEpisode> {
|
||||||
|
val httpUrl = response.request.url
|
||||||
|
val epDetails = preferences.getEpDetails
|
||||||
|
return if (response.request.url.toString().startsWith("$baseUrl/Users/")) {
|
||||||
val data = response.parseAs<ItemDto>()
|
val data = response.parseAs<ItemDto>()
|
||||||
listOf(data.toSEpisode(baseUrl, userId!!, apiKey!!, epDetails, EpisodeType.MOVIE))
|
listOf(data.toSEpisode(baseUrl, userId!!, apiKey!!, epDetails, EpisodeType.MOVIE, prefix))
|
||||||
|
} else if (httpUrl.fragment == "series") {
|
||||||
|
val data = response.parseAs<ItemsDto>()
|
||||||
|
data.items.map {
|
||||||
|
val name = prefix + (it.seasonName?.let { "$it - " } ?: "")
|
||||||
|
it.toSEpisode(baseUrl, userId!!, apiKey!!, epDetails, EpisodeType.EPISODE, name)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val data = response.parseAs<ItemsDto>()
|
val data = response.parseAs<ItemsDto>()
|
||||||
data.items.map {
|
data.items.map {
|
||||||
it.toSEpisode(baseUrl, userId!!, apiKey!!, epDetails, EpisodeType.EPISODE)
|
it.toSEpisode(baseUrl, userId!!, apiKey!!, epDetails, EpisodeType.EPISODE, prefix)
|
||||||
}
|
}
|
||||||
}
|
}.reversed()
|
||||||
|
|
||||||
return episodeList.reversed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EpisodeType {
|
enum class EpisodeType {
|
||||||
@ -441,6 +503,12 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
|
|
||||||
private const val PREF_TRUST_CERT_KEY = "preferred_trust_all_certs"
|
private const val PREF_TRUST_CERT_KEY = "preferred_trust_all_certs"
|
||||||
private const val PREF_TRUST_CERT_DEFAULT = false
|
private const val PREF_TRUST_CERT_DEFAULT = false
|
||||||
|
|
||||||
|
private const val PREF_SPLIT_COLLECTIONS_KEY = "preferred_split_col"
|
||||||
|
private const val PREF_SPLIT_COLLECTIONS_DEFAULT = false
|
||||||
|
|
||||||
|
private const val PREF_SORT_EPISODES_KEY = "preferred_sort_ep"
|
||||||
|
private const val PREF_SORT_EPISODES_DEFAULT = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCustomLabel(): String =
|
private fun getCustomLabel(): String =
|
||||||
@ -592,6 +660,30 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
preferences.edit().putBoolean(key, new).commit()
|
preferences.edit().putBoolean(key, new).commit()
|
||||||
}
|
}
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = PREF_SPLIT_COLLECTIONS_KEY
|
||||||
|
title = "Split collections"
|
||||||
|
summary = "Split each item in a collection into its own entry"
|
||||||
|
setDefaultValue(PREF_SPLIT_COLLECTIONS_DEFAULT)
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val new = newValue as Boolean
|
||||||
|
preferences.edit().putBoolean(key, new).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = PREF_SORT_EPISODES_KEY
|
||||||
|
title = "Sort episodes by release date"
|
||||||
|
summary = "Useful for collections, otherwise items in a collection are grouped by name."
|
||||||
|
setDefaultValue(PREF_SORT_EPISODES_DEFAULT)
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val new = newValue as Boolean
|
||||||
|
preferences.edit().putBoolean(key, new).commit()
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val SharedPreferences.getApiKey
|
private val SharedPreferences.getApiKey
|
||||||
@ -600,9 +692,6 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
private val SharedPreferences.getUserId
|
private val SharedPreferences.getUserId
|
||||||
get() = getString(USERID_KEY, null)
|
get() = getString(USERID_KEY, null)
|
||||||
|
|
||||||
private val SharedPreferences.getHostUrl
|
|
||||||
get() = getString(HOSTURL_KEY, HOSTURL_DEFAULT)!!
|
|
||||||
|
|
||||||
private val SharedPreferences.getUserName
|
private val SharedPreferences.getUserName
|
||||||
get() = getString(USERNAME_KEY, USERNAME_DEFAULT)!!
|
get() = getString(USERNAME_KEY, USERNAME_DEFAULT)!!
|
||||||
|
|
||||||
@ -627,6 +716,12 @@ class Jellyfin(private val suffix: String) : ConfigurableAnimeSource, AnimeHttpS
|
|||||||
private val SharedPreferences.getTrustCert
|
private val SharedPreferences.getTrustCert
|
||||||
get() = getBoolean(PREF_TRUST_CERT_KEY, PREF_TRUST_CERT_DEFAULT)
|
get() = getBoolean(PREF_TRUST_CERT_KEY, PREF_TRUST_CERT_DEFAULT)
|
||||||
|
|
||||||
|
private val SharedPreferences.getSplitCol
|
||||||
|
get() = getBoolean(PREF_SPLIT_COLLECTIONS_KEY, PREF_SPLIT_COLLECTIONS_DEFAULT)
|
||||||
|
|
||||||
|
private val SharedPreferences.sortEp
|
||||||
|
get() = getBoolean(PREF_SORT_EPISODES_KEY, PREF_SORT_EPISODES_DEFAULT)
|
||||||
|
|
||||||
private abstract class MediaLibPreference(context: Context) : ListPreference(context) {
|
private abstract class MediaLibPreference(context: Context) : ListPreference(context) {
|
||||||
abstract fun reload()
|
abstract fun reload()
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ data class ItemDto(
|
|||||||
|
|
||||||
// Only for series, not season
|
// Only for series, not season
|
||||||
@SerialName("Status") val seriesStatus: String? = null,
|
@SerialName("Status") val seriesStatus: String? = null,
|
||||||
|
@SerialName("SeasonName") val seasonName: String? = null,
|
||||||
|
|
||||||
// Episode
|
// Episode
|
||||||
@SerialName("PremiereDate") val premiereData: String? = null,
|
@SerialName("PremiereDate") val premiereData: String? = null,
|
||||||
@ -93,6 +94,14 @@ data class ItemDto(
|
|||||||
httpUrl.fragment("movie")
|
httpUrl.fragment("movie")
|
||||||
title = name
|
title = name
|
||||||
}
|
}
|
||||||
|
"BoxSet" -> {
|
||||||
|
httpUrl.fragment("boxSet")
|
||||||
|
title = name
|
||||||
|
}
|
||||||
|
"Series" -> {
|
||||||
|
httpUrl.fragment("series")
|
||||||
|
title = name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url = httpUrl.build().toString()
|
url = httpUrl.build().toString()
|
||||||
@ -122,15 +131,16 @@ data class ItemDto(
|
|||||||
apiKey: String,
|
apiKey: String,
|
||||||
epDetails: Set<String>,
|
epDetails: Set<String>,
|
||||||
epType: EpisodeType,
|
epType: EpisodeType,
|
||||||
|
prefix: String,
|
||||||
): SEpisode = SEpisode.create().apply {
|
): SEpisode = SEpisode.create().apply {
|
||||||
when (epType) {
|
when (epType) {
|
||||||
EpisodeType.MOVIE -> {
|
EpisodeType.MOVIE -> {
|
||||||
episode_number = 1F
|
episode_number = 1F
|
||||||
name = "Movie"
|
name = "${prefix}Movie"
|
||||||
}
|
}
|
||||||
EpisodeType.EPISODE -> {
|
EpisodeType.EPISODE -> {
|
||||||
episode_number = indexNumber?.toFloat() ?: 1F
|
episode_number = indexNumber?.toFloat() ?: 1F
|
||||||
name = "Ep. $indexNumber - ${this@ItemDto.name}"
|
name = "${prefix}Ep. $indexNumber - ${this@ItemDto.name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user