Choose Seerr quality profile & root folder (#1604)

## Description
Adds option to select the quality profile and/or root folder when
requesting from Seerr.

The user needs "Advanced Requests" permission to use this feature. If
not, the options won't show.

### Related issues
Closes #863
Related to #702

### Testing
Emulator w/ different user permissions and different request
configurations

## Screenshots
<img width="1280" height="720" alt="request_movie Large"
src="https://github.com/user-attachments/assets/2665dcbb-e6c3-4d26-857c-b465edff6a02"
/>


## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-22 18:37:16 -04:00 • committed by GitHub
parent 4431757705
commit 77d7571276
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 899 additions and 138 deletions

View file

@ -6,13 +6,21 @@ import com.github.damontecres.wholphin.api.seerr.model.CreditCrew
import com.github.damontecres.wholphin.api.seerr.model.MediaInfo
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
import com.github.damontecres.wholphin.api.seerr.model.RootFolder
import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResultsInner
import com.github.damontecres.wholphin.api.seerr.model.ServiceProfile
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
import com.github.damontecres.wholphin.api.seerr.model.TvResult
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.SeerrAvailability
import com.github.damontecres.wholphin.data.model.SeerrItemType
import com.github.damontecres.wholphin.data.model.SeerrPermission
import com.github.damontecres.wholphin.data.model.hasPermission
import com.github.damontecres.wholphin.ui.detail.discover.SeerrProfile
import com.github.damontecres.wholphin.ui.detail.discover.SeerrRequestData
import com.github.damontecres.wholphin.ui.detail.discover.SeerrRootFolder
import com.github.damontecres.wholphin.ui.formatBytes
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.toLocalDate
import kotlinx.coroutines.flow.first
@ -191,6 +199,126 @@ class SeerrService
return "${base}${prefix}$path"
}
suspend fun getProfilesAndFolders(type: SeerrItemType): SeerrRequestData {
var result = SeerrRequestData()
val current = seerrServerRepository.current.firstOrNull()
if (current != null && current.config.hasPermission(SeerrPermission.REQUEST_ADVANCED)) {
if (type == SeerrItemType.MOVIE) {
val radarrs = api.serviceApi.serviceRadarrGet()
val radarr =
radarrs.firstOrNull { it.isDefault && !it.is4k }
?: radarrs.firstOrNull { !it.is4k }
val radarr4k =
radarrs.firstOrNull { it.isDefault && it.is4k }
?: radarrs.firstOrNull { it.is4k }
if (radarr != null && radarr.id != null) {
val settings = api.serviceApi.serviceRadarrRadarrIdGet(radarr.id)
setProfilesAndFolder(
settings.profiles,
settings.rootFolders,
settings.server?.activeProfileId,
settings.server?.activeDirectory,
) { profiles, rootFolders ->
result =
result.copy(
serverId = radarr.id,
profiles = profiles,
rootFolders = rootFolders,
)
}
}
if (radarr4k != null && radarr4k.id != null) {
val settings = api.serviceApi.serviceRadarrRadarrIdGet(radarr4k.id)
setProfilesAndFolder(
settings.profiles,
settings.rootFolders,
settings.server?.activeProfileId,
settings.server?.activeDirectory,
) { profiles, rootFolders ->
result =
result.copy(
server4kId = radarr4k.id,
profiles4k = profiles,
rootFolders4k = rootFolders,
)
}
}
} else {
val sonarrs = api.serviceApi.serviceSonarrGet()
val sonarr =
sonarrs.firstOrNull { it.isDefault && !it.is4k }
?: sonarrs.firstOrNull { !it.is4k }
val sonarr4k =
sonarrs.firstOrNull { it.isDefault && it.is4k }
?: sonarrs.firstOrNull { it.is4k }
if (sonarr != null && sonarr.id != null) {
val settings = api.serviceApi.serviceSonarrSonarrIdGet(sonarr.id)
setProfilesAndFolder(
settings.profiles,
settings.rootFolders,
settings.server?.activeProfileId,
settings.server?.activeDirectory,
) { profiles, rootFolders ->
result =
result.copy(
serverId = sonarr.id,
profiles = profiles,
rootFolders = rootFolders,
)
}
}
if (sonarr4k != null && sonarr4k.id != null) {
val settings = api.serviceApi.serviceSonarrSonarrIdGet(sonarr4k.id)
setProfilesAndFolder(
settings.profiles,
settings.rootFolders,
settings.server?.activeProfileId,
settings.server?.activeDirectory,
) { profiles, rootFolders ->
result =
result.copy(
server4kId = sonarr4k.id,
profiles4k = profiles,
rootFolders4k = rootFolders,
)
}
}
}
}
return result
}
private fun setProfilesAndFolder(
profiles: List<ServiceProfile>?,
rootFolders: List<RootFolder>?,
activeProfileId: Int?,
activeDirectory: String?,
setter: (List<SeerrProfile>, List<SeerrRootFolder>) -> Unit,
) {
val profiles =
profiles.orEmpty().mapNotNull {
if (it.id != null && it.name != null) {
SeerrProfile(it.id, it.name, activeProfileId == it.id)
} else {
null
}
}
val rootFolders =
rootFolders.orEmpty().mapNotNull {
if (it.id != null && it.path != null) {
val freeSpace = it.freeSpace?.let { formatBytes(it) } ?: ""
SeerrRootFolder(it.id, it.path, freeSpace, activeDirectory == it.path)
} else {
null
}
}
setter.invoke(profiles, rootFolders)
}
suspend fun createDiscoverItem(item: Any): DiscoverItem =
when (item) {
is MovieResult -> createDiscoverItem(item)