mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-19 05:45:41 +00:00
feat(collections): add TMDB Discover exclusion filters
This commit is contained in:
parent
ee40c8c422
commit
0fc4616b82
5 changed files with 125 additions and 4 deletions
|
|
@ -177,6 +177,9 @@
|
|||
<string name="collections_editor_tmdb_genres_helper">Use TMDB genre numbers. Separate multiple with commas for AND, or pipes for OR.</string>
|
||||
<string name="collections_editor_tmdb_genres_movie_placeholder">28,12</string>
|
||||
<string name="collections_editor_tmdb_genres_series_placeholder">18,35</string>
|
||||
<string name="collections_editor_tmdb_without_genres">Excluded genre IDs</string>
|
||||
<string name="collections_editor_tmdb_without_genres_helper">Exclude titles matching these TMDB genre numbers.</string>
|
||||
<string name="collections_editor_tmdb_without_genres_placeholder">16 for Animation</string>
|
||||
<string name="collections_editor_tmdb_date_from">Release or air date from</string>
|
||||
<string name="collections_editor_tmdb_date_to">Release or air date to</string>
|
||||
<string name="collections_editor_tmdb_date_helper">Use YYYY-MM-DD, for example 2024-01-01.</string>
|
||||
|
|
@ -199,9 +202,15 @@
|
|||
<string name="collections_editor_tmdb_keywords">Keyword IDs</string>
|
||||
<string name="collections_editor_tmdb_keywords_helper">Use TMDB keyword numbers. Quick chips fill common examples.</string>
|
||||
<string name="collections_editor_tmdb_keywords_placeholder">9715 for superhero</string>
|
||||
<string name="collections_editor_tmdb_without_keywords">Excluded keyword IDs</string>
|
||||
<string name="collections_editor_tmdb_without_keywords_helper">Exclude titles matching these TMDB keyword numbers.</string>
|
||||
<string name="collections_editor_tmdb_without_keywords_placeholder">9715</string>
|
||||
<string name="collections_editor_tmdb_companies">Company IDs</string>
|
||||
<string name="collections_editor_tmdb_companies_helper">Use studio/company IDs. Quick chips fill common examples.</string>
|
||||
<string name="collections_editor_tmdb_companies_placeholder">420 for Marvel Studios</string>
|
||||
<string name="collections_editor_tmdb_without_companies">Excluded company IDs</string>
|
||||
<string name="collections_editor_tmdb_without_companies_helper">Exclude titles associated with these TMDB company numbers.</string>
|
||||
<string name="collections_editor_tmdb_without_companies_placeholder">420</string>
|
||||
<string name="collections_editor_tmdb_networks">Network IDs</string>
|
||||
<string name="collections_editor_tmdb_networks_helper">For series only. Use network IDs like Netflix 213 or HBO 49.</string>
|
||||
<string name="collections_editor_tmdb_networks_placeholder">213 for Netflix</string>
|
||||
|
|
@ -282,6 +291,9 @@
|
|||
<string name="collections_editor_tmdb_watch_providers">Watch provider IDs</string>
|
||||
<string name="collections_editor_tmdb_watch_providers_helper">Use TMDB watch provider IDs. Separate multiple with commas for AND, or pipes for OR.</string>
|
||||
<string name="collections_editor_tmdb_watch_providers_placeholder">8|337|350</string>
|
||||
<string name="collections_editor_tmdb_without_watch_providers">Excluded watch provider IDs</string>
|
||||
<string name="collections_editor_tmdb_without_watch_providers_helper">Exclude titles available from these TMDB watch provider numbers in the selected region.</string>
|
||||
<string name="collections_editor_tmdb_without_watch_providers_placeholder">8|337|350</string>
|
||||
<string name="collections_editor_tmdb_quick_watch_providers">Quick watch providers</string>
|
||||
<string name="collections_editor_tmdb_watch_provider_netflix">Netflix</string>
|
||||
<string name="collections_editor_tmdb_watch_provider_prime">Prime Video</string>
|
||||
|
|
|
|||
|
|
@ -1280,6 +1280,17 @@ private fun TmdbSourcePickerScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
TmdbFilterField(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_without_genres),
|
||||
helper = stringResource(Res.string.collections_editor_tmdb_without_genres_helper),
|
||||
value = state.tmdbFilters.withoutGenres.orEmpty(),
|
||||
placeholder = stringResource(Res.string.collections_editor_tmdb_without_genres_placeholder),
|
||||
onValueChange = { value ->
|
||||
CollectionEditorRepository.updateTmdbFilters {
|
||||
it.copy(withoutGenres = value.ifBlank { null })
|
||||
}
|
||||
},
|
||||
)
|
||||
TmdbFilterField(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_date_from),
|
||||
helper = stringResource(Res.string.collections_editor_tmdb_date_helper),
|
||||
|
|
@ -1406,6 +1417,17 @@ private fun TmdbSourcePickerScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
TmdbFilterField(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_without_keywords),
|
||||
helper = stringResource(Res.string.collections_editor_tmdb_without_keywords_helper),
|
||||
value = state.tmdbFilters.withoutKeywords.orEmpty(),
|
||||
placeholder = stringResource(Res.string.collections_editor_tmdb_without_keywords_placeholder),
|
||||
onValueChange = { value ->
|
||||
CollectionEditorRepository.updateTmdbFilters {
|
||||
it.copy(withoutKeywords = value.ifBlank { null })
|
||||
}
|
||||
},
|
||||
)
|
||||
TmdbQuickChips(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_quick_studios),
|
||||
chips = listOf(
|
||||
|
|
@ -1430,6 +1452,17 @@ private fun TmdbSourcePickerScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
TmdbFilterField(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_without_companies),
|
||||
helper = stringResource(Res.string.collections_editor_tmdb_without_companies_helper),
|
||||
value = state.tmdbFilters.withoutCompanies.orEmpty(),
|
||||
placeholder = stringResource(Res.string.collections_editor_tmdb_without_companies_placeholder),
|
||||
onValueChange = { value ->
|
||||
CollectionEditorRepository.updateTmdbFilters {
|
||||
it.copy(withoutCompanies = value.ifBlank { null })
|
||||
}
|
||||
},
|
||||
)
|
||||
TmdbQuickChips(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_quick_networks),
|
||||
chips = listOf(
|
||||
|
|
@ -1489,6 +1522,17 @@ private fun TmdbSourcePickerScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
TmdbFilterField(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_without_watch_providers),
|
||||
helper = stringResource(Res.string.collections_editor_tmdb_without_watch_providers_helper),
|
||||
value = state.tmdbFilters.withoutWatchProviders.orEmpty(),
|
||||
placeholder = stringResource(Res.string.collections_editor_tmdb_without_watch_providers_placeholder),
|
||||
onValueChange = { value ->
|
||||
CollectionEditorRepository.updateTmdbFilters {
|
||||
it.copy(withoutWatchProviders = value.ifBlank { null })
|
||||
}
|
||||
},
|
||||
)
|
||||
TmdbQuickChips(
|
||||
label = stringResource(Res.string.collections_editor_tmdb_quick_watch_regions),
|
||||
chips = listOf(
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ enum class TraktSortHow(val value: String) {
|
|||
@Serializable
|
||||
data class TmdbCollectionFilters(
|
||||
val withGenres: String? = null,
|
||||
val withoutGenres: String? = null,
|
||||
val releaseDateGte: String? = null,
|
||||
val releaseDateLte: String? = null,
|
||||
val voteAverageGte: Double? = null,
|
||||
|
|
@ -162,11 +163,14 @@ data class TmdbCollectionFilters(
|
|||
val withOriginalLanguage: String? = null,
|
||||
val withOriginCountry: String? = null,
|
||||
val withKeywords: String? = null,
|
||||
val withoutKeywords: String? = null,
|
||||
val withCompanies: String? = null,
|
||||
val withoutCompanies: String? = null,
|
||||
val withNetworks: String? = null,
|
||||
val year: Int? = null,
|
||||
val watchRegion: String? = null,
|
||||
val withWatchProviders: String? = null,
|
||||
val withoutWatchProviders: String? = null,
|
||||
)
|
||||
|
||||
data class TmdbSourceImportMetadata(
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ object TmdbCollectionSourceResolver {
|
|||
)
|
||||
}
|
||||
|
||||
private fun buildDiscoverQuery(
|
||||
internal fun buildDiscoverQuery(
|
||||
source: CollectionSource,
|
||||
sourceType: TmdbCollectionSourceType,
|
||||
mediaType: TmdbCollectionMediaType,
|
||||
|
|
@ -331,18 +331,26 @@ object TmdbCollectionSourceResolver {
|
|||
val companyId = source.tmdbId?.toString().takeIf { sourceType == TmdbCollectionSourceType.COMPANY }
|
||||
val networkId = source.tmdbId?.toString().takeIf { sourceType == TmdbCollectionSourceType.NETWORK }
|
||||
putIfNotBlank("with_companies", companyId ?: filters.withCompanies)
|
||||
putIfNotBlank("without_companies", filters.withoutCompanies)
|
||||
putIfNotBlank("with_networks", networkId ?: filters.withNetworks)
|
||||
putIfNotBlank("with_genres", filters.withGenres)
|
||||
putIfNotBlank("without_genres", filters.withoutGenres)
|
||||
putIfNotBlank("vote_count.gte", filters.voteCountGte?.toString())
|
||||
putIfNotBlank("vote_average.gte", filters.voteAverageGte?.toString())
|
||||
putIfNotBlank("vote_average.lte", filters.voteAverageLte?.toString())
|
||||
putIfNotBlank("with_original_language", filters.withOriginalLanguage)
|
||||
putIfNotBlank("with_origin_country", filters.withOriginCountry)
|
||||
putIfNotBlank("with_keywords", filters.withKeywords)
|
||||
if (!filters.withWatchProviders.isNullOrBlank()) {
|
||||
put("with_watch_providers", filters.withWatchProviders)
|
||||
putIfNotBlank("without_keywords", filters.withoutKeywords)
|
||||
val withWatchProviders = filters.withWatchProviders?.takeIf { it.isNotBlank() }
|
||||
val withoutWatchProviders = filters.withoutWatchProviders?.takeIf { it.isNotBlank() }
|
||||
if (withWatchProviders != null || withoutWatchProviders != null) {
|
||||
putIfNotBlank("with_watch_providers", withWatchProviders)
|
||||
putIfNotBlank("without_watch_providers", withoutWatchProviders)
|
||||
put("watch_region", filters.watchRegion?.takeIf { it.isNotBlank() } ?: "US")
|
||||
put("with_watch_monetization_types", "flatrate|free|ads|rent|buy")
|
||||
if (withWatchProviders != null) {
|
||||
put("with_watch_monetization_types", "flatrate|free|ads|rent|buy")
|
||||
}
|
||||
}
|
||||
putIfNotBlank("year", filters.year?.takeIf { mediaType == TmdbCollectionMediaType.MOVIE }?.toString())
|
||||
putIfNotBlank("first_air_date_year", filters.year?.takeIf { mediaType == TmdbCollectionMediaType.TV }?.toString())
|
||||
|
|
|
|||
|
|
@ -57,6 +57,59 @@ class CollectionSourceSerializationTest {
|
|||
assertEquals(TraktSortHow.DESC.value, source.sortHow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tmdbExclusionFiltersRoundTripAndBuildDiscoverQuery() {
|
||||
val filters = TmdbCollectionFilters(
|
||||
withoutGenres = "16",
|
||||
withoutKeywords = "9715|818",
|
||||
withoutCompanies = "420",
|
||||
withoutWatchProviders = "8|337",
|
||||
)
|
||||
val collection = Collection(
|
||||
id = "collection-1",
|
||||
title = "Live Action",
|
||||
folders = listOf(
|
||||
CollectionFolder(
|
||||
id = "folder-1",
|
||||
title = "Movies",
|
||||
sources = listOf(
|
||||
CollectionSource(
|
||||
provider = "tmdb",
|
||||
tmdbSourceType = TmdbCollectionSourceType.DISCOVER.name,
|
||||
title = "Without Animation",
|
||||
mediaType = TmdbCollectionMediaType.MOVIE.name,
|
||||
filters = filters,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val encoded = json.encodeToString(listOf(collection))
|
||||
val decodedSource = json.decodeFromString<List<Collection>>(encoded)
|
||||
.single()
|
||||
.folders
|
||||
.single()
|
||||
.resolvedSources
|
||||
.single()
|
||||
assertEquals(filters, decodedSource.filters)
|
||||
|
||||
val query = TmdbCollectionSourceResolver.buildDiscoverQuery(
|
||||
source = decodedSource,
|
||||
sourceType = TmdbCollectionSourceType.DISCOVER,
|
||||
mediaType = TmdbCollectionMediaType.MOVIE,
|
||||
language = "en-US",
|
||||
page = 1,
|
||||
filters = decodedSource.filters ?: TmdbCollectionFilters(),
|
||||
)
|
||||
assertEquals("16", query["without_genres"])
|
||||
assertEquals("9715|818", query["without_keywords"])
|
||||
assertEquals("420", query["without_companies"])
|
||||
assertEquals("8|337", query["without_watch_providers"])
|
||||
assertEquals("US", query["watch_region"])
|
||||
assertFalse("with_watch_monetization_types" in query)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun importedTraktSourceWithoutListIdIsRejected() {
|
||||
val payload = """
|
||||
|
|
|
|||
Loading…
Reference in a new issue