diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt index ff168c525..7f3114e34 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/streams/StreamsRepository.kt @@ -15,6 +15,7 @@ import com.nuvio.app.features.player.PlayerSettingsRepository import com.nuvio.app.features.plugins.PluginRepository import com.nuvio.app.features.plugins.pluginContentId import com.nuvio.app.features.plugins.PluginsUiState +import com.nuvio.app.features.tmdb.TmdbService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -460,13 +461,22 @@ object StreamsRepository { } } + // Most scrapers expect a numeric TMDB id, but videoId is frequently an + // IMDb id (e.g. from Cinemeta). Resolve it once up front so every scraper + // gets a usable id instead of each independently failing TMDB lookups. + val resolvedPluginTmdbId = if (pluginProviderGroups.isNotEmpty()) { + runCatchingUnlessCancelled { TmdbService.ensureTmdbId(videoId, type) }.getOrNull() + } else { + null + } + pluginProviderGroups.forEach { providerGroup -> val includeScraperNameInSubtitle = false providerGroup.scrapers.forEach { scraper -> launch { val completion = PluginRepository.executeScraper( scraper = scraper, - tmdbId = pluginContentId( + tmdbId = resolvedPluginTmdbId ?: pluginContentId( videoId = videoId, season = season, episode = episode, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tmdb/TmdbService.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tmdb/TmdbService.kt index 4e7e88486..49b6e73ab 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tmdb/TmdbService.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tmdb/TmdbService.kt @@ -99,8 +99,13 @@ object TmdbService { }.getOrNull() } + // Public demo TMDB v3 key, used as a fallback so IMDb->TMDB id resolution for + // plugin scrapers works even when the user hasn't configured a personal TMDB + // API key. This is the same key several community scraper plugins embed directly. + private const val FALLBACK_API_KEY = "439c478a771f35c05022f9feabcca01c" + private fun currentApiKey(): String? = - TmdbSettingsRepository.snapshot().apiKey.trim().takeIf(String::isNotBlank) + TmdbSettingsRepository.snapshot().apiKey.trim().takeIf(String::isNotBlank) ?: FALLBACK_API_KEY internal fun normalizeMediaType(mediaType: String): String = when (mediaType.trim().lowercase()) {