Updates to Seerr image fetching (#1086)

## Description
This PR makes changes to how images are fetched for discover/Seerr
items.

If the discovered item is available on the Jellyfin server, the images
will be fetched from the Jellyfin server.

Then if the Seerr server has image caching enabled, the images will be
fetched from the Seerr server. Otherwise, images are fetched from TMDB .
TMDB was the previous behavior for all images.

### Dev notes

This PR also updates the Seerr server URLs stored in the database, so
it's not backwards compatible.

### Related issues
Fixes #1079

### Testing
Emulator against Jellyseer 2.7.3 & Seerr 3.0.1

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-03-14 16:47:08 -04:00 • committed by GitHub
parent 2da55bb616
commit 627b89f1db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 536 additions and 327 deletions

View file

@ -103,7 +103,7 @@ class DiscoverMovieViewModel
) {
Timber.v("Init for movie %s", item.id)
val movie = fetchAndSetItem().await()
val discoveredItem = DiscoverItem(movie)
val discoveredItem = seerrService.createDiscoverItem(movie)
backdropService.submit(discoveredItem)
updateCanCancel()
@ -121,7 +121,7 @@ class DiscoverMovieViewModel
seerrService.api.moviesApi
.movieMovieIdSimilarGet(movieId = item.id, page = 1)
.results
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
similar.setValueOnMain(result)
}
@ -130,7 +130,7 @@ class DiscoverMovieViewModel
seerrService.api.moviesApi
.movieMovieIdRecommendationsGet(movieId = item.id, page = 1)
.results
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
recommended.setValueOnMain(result)
}
@ -138,11 +138,11 @@ class DiscoverMovieViewModel
val people =
movie.credits
?.cast
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty() +
movie.credits
?.crew
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
this@DiscoverMovieViewModel.people.setValueOnMain(people)
val trailers =

View file

@ -67,11 +67,11 @@ class DiscoverPersonViewModel
.let { credits ->
val cast =
credits.cast
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
val crew =
credits.crew
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
cast + crew
}

View file

@ -102,7 +102,7 @@ class DiscoverSeriesViewModel
) {
Timber.v("Init for tv %s", item.id)
val tv = fetchAndSetItem().await()
val discoveredItem = DiscoverItem(tv)
val discoveredItem = seerrService.createDiscoverItem(tv)
backdropService.submit(discoveredItem)
updateSeasonStatus()
@ -121,7 +121,7 @@ class DiscoverSeriesViewModel
seerrService.api.tvApi
.tvTvIdSimilarGet(tvId = item.id, page = 1)
.results
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
similar.setValueOnMain(result)
}
@ -130,7 +130,7 @@ class DiscoverSeriesViewModel
seerrService.api.tvApi
.tvTvIdRecommendationsGet(tvId = item.id, page = 1)
.results
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
recommended.setValueOnMain(result)
}
@ -138,11 +138,11 @@ class DiscoverSeriesViewModel
val people =
tv.credits
?.cast
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty() +
tv.credits
?.crew
?.map(::DiscoverItem)
?.map { seerrService.createDiscoverItem(it) }
.orEmpty()
this@DiscoverSeriesViewModel.people.setValueOnMain(people)

View file

@ -239,7 +239,9 @@ class SeriesViewModel
val tv =
if (active) {
try {
seerrService.getTvSeries(item)?.let { DiscoverItem(it) }
seerrService
.getTvSeries(item)
?.let { seerrService.createDiscoverItem(it) }
} catch (ex: Exception) {
Timber.e(ex)
null

View file

@ -85,13 +85,13 @@ class SeerrRequestsViewModel
seerrService.api.moviesApi
.movieMovieIdGet(
movieId = request.media.tmdbId,
).let { DiscoverItem(it) }
).let { seerrService.createDiscoverItem(it) }
}
SeerrItemType.TV -> {
seerrService.api.tvApi
.tvTvIdGet(tvId = request.media.tmdbId)
.let { DiscoverItem(it) }
.let { seerrService.createDiscoverItem(it) }
}
SeerrItemType.PERSON -> {

View file

@ -165,7 +165,7 @@ class SearchViewModel
val results =
seerrService
.search(query)
.map { DiscoverItem(it) }
.map { seerrService.createDiscoverItem(it) }
.filter { it.type == SeerrItemType.MOVIE || it.type == SeerrItemType.TV }
seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results))
}

View file

@ -127,37 +127,50 @@ class SwitchSeerrViewModel
}
fun createUrls(url: String): List<HttpUrl> {
val urls = mutableListOf<String>()
val urls = mutableListOf<HttpUrl>()
if (url.startsWith("http://") || url.startsWith("https://")) {
urls.add(url)
val httpUrl = url.toHttpUrl()
urls.add(httpUrl)
if (HttpUrl.defaultPort(httpUrl.scheme) == httpUrl.port) {
urls.add("$url:5055")
urls.add(httpUrl.newBuilder().port(5055).build())
}
} else {
urls.add("http://$url")
val httpUrl = "http://$url".toHttpUrl()
urls.add(httpUrl)
if (httpUrl.port == 80) {
urls.add("https://$url")
urls.add("http://$url:5055")
urls.add("https://$url:5055")
urls.add(httpUrl.newBuilder().scheme("https").build())
urls.add(httpUrl.newBuilder().port(5055).build())
urls.add(
httpUrl
.newBuilder()
.scheme("https")
.port(5055)
.build(),
)
} else {
urls.add("https://$url")
urls.add(httpUrl.newBuilder().scheme("https").build())
}
}
return urls.map { cleanUrl(it).toHttpUrl() }
return urls
}
private fun cleanUrl(url: String) =
if (!url.endsWith("/api/v1")) {
fun createSeerrApiUrl(url: String): String =
if (url.isBlank()) {
url
} else if (url.endsWith("/api/v1") || url.endsWith("/api/v1/")) {
url
} else {
url
.toHttpUrl()
.newBuilder()
.apply {
addPathSegment("api")
addPathSegment("v1")
}.build()
.addPathSegment("api")
.addPathSegment("v1")
.build()
.toString()
} else {
url
}
fun migrateSeerrUrl(url: String): String {
var url = url.removeSuffix("/api/v1/").removeSuffix("/api/v1")
if (!url.endsWith("/")) url += "/"
return url
}