mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-30 08:09:21 +00:00
Merge 0be43e2771 into f1e4f88bc3
This commit is contained in:
commit
098563bb1d
13 changed files with 154 additions and 29 deletions
|
|
@ -38,6 +38,7 @@ data class MetaDetails(
|
|||
val trailers: List<MetaTrailer> = emptyList(),
|
||||
val links: List<MetaLink> = emptyList(),
|
||||
val videos: List<MetaVideo> = emptyList(),
|
||||
val imdbId: String? = null,
|
||||
)
|
||||
|
||||
enum class MoreLikeThisSource {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ internal object MetaDetailsParser {
|
|||
trailers = meta.trailers(),
|
||||
links = links,
|
||||
videos = meta.videos(),
|
||||
imdbId = meta.string("imdb_id") ?: meta.string("imdbId"),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,11 +261,17 @@ fun MetaDetailsScreen(
|
|||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val imdbId = extractImdbId(metaForRatings.id) ?: extractImdbId(id)
|
||||
val imdbId = metaForRatings.imdbId?.takeIf { it.startsWith("tt", ignoreCase = true) }
|
||||
?: extractImdbId(metaForRatings.id)
|
||||
?: extractImdbId(id)
|
||||
val tmdbId = extractTmdbId(metaForRatings.id)
|
||||
?: extractTmdbId(id)
|
||||
?: TmdbService.ensureTmdbId(metaForRatings.id, metaForRatings.type)?.toIntOrNull()
|
||||
?: TmdbService.ensureTmdbId(id, type)?.toIntOrNull()
|
||||
?: TmdbService.ensureTmdbId(
|
||||
metaForRatings.id,
|
||||
metaForRatings.type,
|
||||
metaForRatings.imdbId,
|
||||
)?.toIntOrNull()
|
||||
?: TmdbService.ensureTmdbId(id, type, metaForRatings.imdbId)?.toIntOrNull()
|
||||
|
||||
if (imdbId == null && tmdbId == null) {
|
||||
episodeImdbRatings = emptyMap()
|
||||
|
|
@ -1565,6 +1571,7 @@ private fun MetaDetails.toMetaPreview(): MetaPreview =
|
|||
releaseInfo = releaseInfo,
|
||||
imdbRating = imdbRating,
|
||||
genres = genres,
|
||||
imdbId = imdbId,
|
||||
)
|
||||
|
||||
private fun LazyListScope.configuredMetaSectionItems(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ internal object HomeCatalogParser {
|
|||
genres = meta.array("genres").mapNotNull { genre ->
|
||||
genre.jsonPrimitive.contentOrNull?.takeIf { it.isNotBlank() }
|
||||
},
|
||||
imdbId = meta.string("imdb_id") ?: meta.string("imdbId"),
|
||||
)
|
||||
if (seenKeys.add(item.stableKey())) {
|
||||
add(item)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ data class MetaPreview(
|
|||
val voteCount: Int? = null,
|
||||
val imdbRating: String? = null,
|
||||
val genres: List<String> = emptyList(),
|
||||
val imdbId: String? = null,
|
||||
)
|
||||
|
||||
fun MetaPreview.stableKey(): String = "$type:$id"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ object MdbListMetadataService {
|
|||
if (!settings.enabled) return false
|
||||
if (settings.apiKey.trim().isBlank()) return false
|
||||
if (settings.enabledProvidersInPriorityOrder().isEmpty()) return false
|
||||
return extractImdbId(meta.id) != null || extractImdbId(fallbackItemId) != null
|
||||
return validImdbId(meta.imdbId) != null ||
|
||||
extractImdbId(meta.id) != null ||
|
||||
extractImdbId(fallbackItemId) != null
|
||||
}
|
||||
|
||||
suspend fun enrichMeta(
|
||||
|
|
@ -62,7 +64,8 @@ object MdbListMetadataService {
|
|||
}
|
||||
val apiKey = settings.apiKey.trim()
|
||||
|
||||
val imdbId = extractImdbId(meta.id)
|
||||
val imdbId = validImdbId(meta.imdbId)
|
||||
?: extractImdbId(meta.id)
|
||||
?: extractImdbId(fallbackItemId)
|
||||
?: return meta.copy(externalRatings = emptyList())
|
||||
val mediaType = toMdbListMediaType(meta.type)
|
||||
|
|
@ -108,6 +111,11 @@ object MdbListMetadataService {
|
|||
ratings
|
||||
}
|
||||
|
||||
private fun validImdbId(value: String?): String? =
|
||||
value
|
||||
?.trim()
|
||||
?.takeIf { imdbRegex.matches(it) }
|
||||
|
||||
private suspend fun fetchProviderRating(
|
||||
imdbId: String,
|
||||
mediaType: String,
|
||||
|
|
|
|||
|
|
@ -524,8 +524,8 @@ object TmdbMetadataService {
|
|||
if (!settings.enabled || !settings.hasApiKey) return meta
|
||||
|
||||
val tmdbType = normalizeMetaType(meta.type)
|
||||
val tmdbId = TmdbService.ensureTmdbId(meta.id, tmdbType)
|
||||
?: TmdbService.ensureTmdbId(fallbackItemId, tmdbType)
|
||||
val tmdbId = TmdbService.ensureTmdbId(meta.id, tmdbType, meta.imdbId)
|
||||
?: TmdbService.ensureTmdbId(fallbackItemId, tmdbType, meta.imdbId)
|
||||
?: return meta
|
||||
|
||||
val needsEpisodes = (
|
||||
|
|
|
|||
|
|
@ -15,22 +15,26 @@ object TmdbService {
|
|||
private val tmdbToImdbCache = linkedMapOf<String, String>()
|
||||
private val cacheMutex = Mutex()
|
||||
|
||||
suspend fun ensureTmdbId(videoId: String, mediaType: String): String? {
|
||||
suspend fun ensureTmdbId(
|
||||
videoId: String,
|
||||
mediaType: String,
|
||||
fallbackImdbId: String? = null,
|
||||
): String? {
|
||||
val apiKey = currentApiKey() ?: return null
|
||||
val normalizedType = normalizeMediaType(mediaType)
|
||||
|
||||
val normalized = videoId
|
||||
.removePrefix("tmdb:")
|
||||
.removePrefix("movie:")
|
||||
.removePrefix("series:")
|
||||
.substringBefore(':')
|
||||
.substringBefore('/')
|
||||
.trim()
|
||||
tmdbLookupCandidates(videoId, fallbackImdbId).forEach { candidate ->
|
||||
if (candidate.all(Char::isDigit)) return candidate
|
||||
if (candidate.startsWith("tt", ignoreCase = true)) {
|
||||
imdbToTmdb(
|
||||
imdbId = candidate,
|
||||
mediaType = normalizedType,
|
||||
apiKey = apiKey,
|
||||
)?.let { return it }
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized.isBlank()) return null
|
||||
if (normalized.all(Char::isDigit)) return normalized
|
||||
if (!normalized.startsWith("tt", ignoreCase = true)) return null
|
||||
|
||||
return imdbToTmdb(imdbId = normalized, mediaType = mediaType, apiKey = apiKey)
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun tmdbToImdb(tmdbId: Int, mediaType: String): String? {
|
||||
|
|
@ -110,6 +114,24 @@ object TmdbService {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun tmdbLookupCandidates(
|
||||
videoId: String,
|
||||
fallbackImdbId: String?,
|
||||
): List<String> =
|
||||
listOfNotNull(videoId, fallbackImdbId)
|
||||
.mapNotNull { rawId ->
|
||||
rawId
|
||||
.trim()
|
||||
.removePrefix("tmdb:")
|
||||
.removePrefix("movie:")
|
||||
.removePrefix("series:")
|
||||
.substringBefore(':')
|
||||
.substringBefore('/')
|
||||
.trim()
|
||||
.takeIf(String::isNotBlank)
|
||||
}
|
||||
.distinct()
|
||||
|
||||
internal fun buildTmdbUrl(
|
||||
endpoint: String,
|
||||
apiKey: String,
|
||||
|
|
|
|||
|
|
@ -106,12 +106,19 @@ object TraktRelatedRepository {
|
|||
): ResolvedRelatedTarget? {
|
||||
val type = resolveRelatedType(meta = meta, fallbackItemType = fallbackItemType) ?: return null
|
||||
resolveDirectPathId(meta.id)?.let { return ResolvedRelatedTarget(type, it) }
|
||||
resolveDirectPathId(meta.imdbId)?.let { return ResolvedRelatedTarget(type, it) }
|
||||
resolveDirectPathId(fallbackItemId)?.let { return ResolvedRelatedTarget(type, it) }
|
||||
|
||||
val tmdbId = resolveTmdbCandidate(meta.id)
|
||||
?: resolveTmdbCandidate(fallbackItemId)
|
||||
?: TmdbService.ensureTmdbId(meta.id, meta.type)?.toIntOrNull()
|
||||
?: fallbackItemId?.let { TmdbService.ensureTmdbId(it, fallbackItemType ?: meta.type) }?.toIntOrNull()
|
||||
?: TmdbService.ensureTmdbId(meta.id, meta.type, meta.imdbId)?.toIntOrNull()
|
||||
?: fallbackItemId?.let {
|
||||
TmdbService.ensureTmdbId(
|
||||
it,
|
||||
fallbackItemType ?: meta.type,
|
||||
meta.imdbId,
|
||||
)
|
||||
}?.toIntOrNull()
|
||||
?: return null
|
||||
|
||||
return resolveViaTraktSearch(type = type, tmdbId = tmdbId, headers = headers)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ class MetaDetailsParserTest {
|
|||
{
|
||||
"id": "mal:62516",
|
||||
"type": "series",
|
||||
"name": "The Fragrant Flower Blooms with Dignity"
|
||||
"name": "The Fragrant Flower Blooms with Dignity",
|
||||
"imdb_id": "tt30217403"
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
|
@ -30,6 +31,7 @@ class MetaDetailsParserTest {
|
|||
assertEquals("mal:62516", result.id)
|
||||
assertEquals("series", result.type)
|
||||
assertEquals("The Fragrant Flower Blooms with Dignity", result.name)
|
||||
assertEquals("tt30217403", result.imdbId)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -9,12 +9,17 @@ class HomeCatalogParserTest {
|
|||
fun `parse catalog response de-duplicates repeated metas but preserves raw count`() {
|
||||
val result = HomeCatalogParser.parseCatalogResponse(
|
||||
"""
|
||||
{
|
||||
"metas": [
|
||||
{ "id": "mal:62516", "type": "series", "name": "A" },
|
||||
{ "id": "mal:62516", "type": "series", "name": "A duplicate" },
|
||||
{ "id": "mal:1", "type": "movie", "name": "B" }
|
||||
]
|
||||
{
|
||||
"metas": [
|
||||
{
|
||||
"id": "mal:62516",
|
||||
"type": "series",
|
||||
"name": "A",
|
||||
"imdb_id": "tt30217403"
|
||||
},
|
||||
{ "id": "mal:62516", "type": "series", "name": "A duplicate" },
|
||||
{ "id": "mal:1", "type": "movie", "name": "B" }
|
||||
]
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
|
@ -25,6 +30,7 @@ class HomeCatalogParserTest {
|
|||
result.items.map { it.stableKey() },
|
||||
)
|
||||
assertEquals("A", result.items.first().name)
|
||||
assertEquals("tt30217403", result.items.first().imdbId)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.nuvio.app.features.mdblist
|
||||
|
||||
import com.nuvio.app.features.details.MetaDetails
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MdbListMetadataServiceTest {
|
||||
|
||||
@Test
|
||||
fun `addon imdb alias enables ratings for anime id`() {
|
||||
val meta = MetaDetails(
|
||||
id = "mal:49894",
|
||||
type = "series",
|
||||
name = "Hero Classroom",
|
||||
imdbId = "tt28254942",
|
||||
)
|
||||
|
||||
assertTrue(
|
||||
MdbListMetadataService.shouldFetchForMeta(
|
||||
meta = meta,
|
||||
fallbackItemId = meta.id,
|
||||
settings = MdbListSettings(
|
||||
enabled = true,
|
||||
apiKey = "test",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.nuvio.app.features.tmdb
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TmdbServiceTest {
|
||||
|
||||
@Test
|
||||
fun `anime id keeps addon imdb alias as fallback candidate`() {
|
||||
assertEquals(
|
||||
listOf("mal", "tt28254942"),
|
||||
tmdbLookupCandidates(
|
||||
videoId = "mal:49894",
|
||||
fallbackImdbId = "tt28254942",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `numeric tmdb id remains ahead of fallback imdb alias`() {
|
||||
assertEquals(
|
||||
listOf("228234", "tt28254942"),
|
||||
tmdbLookupCandidates(
|
||||
videoId = "tmdb:228234",
|
||||
fallbackImdbId = "tt28254942",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `duplicate primary and fallback ids are resolved once`() {
|
||||
assertEquals(
|
||||
listOf("tt0133093"),
|
||||
tmdbLookupCandidates(
|
||||
videoId = "tt0133093:1:1",
|
||||
fallbackImdbId = "tt0133093",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue