mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 07:21:58 +00:00
ref: plugin to use saved tmdb api key for tv show resolution
This commit is contained in:
parent
9a0acf7149
commit
023c497fa8
6 changed files with 129 additions and 20 deletions
|
|
@ -7,6 +7,7 @@ import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||||
import com.nuvio.app.features.addons.httpGetText
|
import com.nuvio.app.features.addons.httpGetText
|
||||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||||
import com.nuvio.app.features.plugins.PluginRepository
|
import com.nuvio.app.features.plugins.PluginRepository
|
||||||
|
import com.nuvio.app.features.plugins.pluginContentId
|
||||||
import com.nuvio.app.features.plugins.PluginRuntimeResult
|
import com.nuvio.app.features.plugins.PluginRuntimeResult
|
||||||
import com.nuvio.app.features.plugins.PluginScraper
|
import com.nuvio.app.features.plugins.PluginScraper
|
||||||
import com.nuvio.app.features.streams.AddonStreamGroup
|
import com.nuvio.app.features.streams.AddonStreamGroup
|
||||||
|
|
@ -243,7 +244,11 @@ object PlayerStreamsRepository {
|
||||||
async {
|
async {
|
||||||
PluginRepository.executeScraper(
|
PluginRepository.executeScraper(
|
||||||
scraper = scraper,
|
scraper = scraper,
|
||||||
tmdbId = videoId.toPluginTmdbId(),
|
tmdbId = pluginContentId(
|
||||||
|
videoId = videoId,
|
||||||
|
season = season,
|
||||||
|
episode = episode,
|
||||||
|
),
|
||||||
mediaType = type,
|
mediaType = type,
|
||||||
season = season,
|
season = season,
|
||||||
episode = episode,
|
episode = episode,
|
||||||
|
|
@ -339,11 +344,3 @@ private fun PluginRuntimeResult.toStreamItem(scraper: PluginScraper): StreamItem
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toPluginTmdbId(): String {
|
|
||||||
return when {
|
|
||||||
startsWith("tmdb:") -> removePrefix("tmdb:").substringBefore(":").ifBlank { this }
|
|
||||||
startsWith("tmdb/") -> removePrefix("tmdb/").substringBefore('/').ifBlank { this }
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.nuvio.app.features.plugins
|
||||||
|
|
||||||
|
internal fun pluginContentId(
|
||||||
|
videoId: String,
|
||||||
|
season: Int?,
|
||||||
|
episode: Int?,
|
||||||
|
): String {
|
||||||
|
val trimmed = videoId.trim()
|
||||||
|
if (trimmed.isBlank()) return videoId
|
||||||
|
|
||||||
|
val withoutPrefix = when {
|
||||||
|
trimmed.startsWith("tmdb:") -> trimmed.removePrefix("tmdb:")
|
||||||
|
trimmed.startsWith("tmdb/") -> trimmed.removePrefix("tmdb/")
|
||||||
|
else -> trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
val withoutEpisodeSuffix = if (season != null && episode != null) {
|
||||||
|
withoutPrefix.removeSuffix(":$season:$episode")
|
||||||
|
} else {
|
||||||
|
withoutPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return withoutEpisodeSuffix.substringBefore('/').ifBlank { trimmed }
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import com.nuvio.app.features.addons.httpGetText
|
||||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||||
import com.nuvio.app.features.player.PlayerSettingsRepository
|
import com.nuvio.app.features.player.PlayerSettingsRepository
|
||||||
import com.nuvio.app.features.plugins.PluginRepository
|
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.plugins.PluginsUiState
|
||||||
import com.nuvio.app.features.plugins.PluginRepositoryItem
|
import com.nuvio.app.features.plugins.PluginRepositoryItem
|
||||||
import com.nuvio.app.features.plugins.PluginRuntimeResult
|
import com.nuvio.app.features.plugins.PluginRuntimeResult
|
||||||
|
|
@ -285,7 +286,11 @@ object StreamsRepository {
|
||||||
launch {
|
launch {
|
||||||
val completion = PluginRepository.executeScraper(
|
val completion = PluginRepository.executeScraper(
|
||||||
scraper = scraper,
|
scraper = scraper,
|
||||||
tmdbId = videoId.toPluginTmdbId(),
|
tmdbId = pluginContentId(
|
||||||
|
videoId = videoId,
|
||||||
|
season = season,
|
||||||
|
episode = episode,
|
||||||
|
),
|
||||||
mediaType = type,
|
mediaType = type,
|
||||||
season = season,
|
season = season,
|
||||||
episode = episode,
|
episode = episode,
|
||||||
|
|
@ -486,14 +491,6 @@ private fun List<AddonStreamGroup>.toEmptyStateReason(anyLoading: Boolean): Stre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toPluginTmdbId(): String {
|
|
||||||
return when {
|
|
||||||
startsWith("tmdb:") -> removePrefix("tmdb:").substringBefore(":").ifBlank { this }
|
|
||||||
startsWith("tmdb/") -> removePrefix("tmdb/").substringBefore('/').ifBlank { this }
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PluginRuntimeResult.toStreamItem(
|
private fun PluginRuntimeResult.toStreamItem(
|
||||||
scraper: PluginScraper,
|
scraper: PluginScraper,
|
||||||
addonName: String = scraper.name,
|
addonName: String = scraper.name,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.nuvio.app.features.plugins
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PluginContentIdsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `series playback id strips season episode suffix`() {
|
||||||
|
assertEquals(
|
||||||
|
"tt2575988",
|
||||||
|
pluginContentId(
|
||||||
|
videoId = "tt2575988:5:8",
|
||||||
|
season = 5,
|
||||||
|
episode = 8,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tmdb prefixed series playback id strips prefix and suffix`() {
|
||||||
|
assertEquals(
|
||||||
|
"12345",
|
||||||
|
pluginContentId(
|
||||||
|
videoId = "tmdb:12345:2:6",
|
||||||
|
season = 2,
|
||||||
|
episode = 6,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `movie id stays unchanged`() {
|
||||||
|
assertEquals(
|
||||||
|
"tt0133093",
|
||||||
|
pluginContentId(
|
||||||
|
videoId = "tt0133093",
|
||||||
|
season = null,
|
||||||
|
episode = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `slash prefixed tmdb id keeps base content id`() {
|
||||||
|
assertEquals(
|
||||||
|
"999",
|
||||||
|
pluginContentId(
|
||||||
|
videoId = "tmdb/999/1/2",
|
||||||
|
season = 1,
|
||||||
|
episode = 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger
|
||||||
import com.nuvio.app.core.network.SupabaseProvider
|
import com.nuvio.app.core.network.SupabaseProvider
|
||||||
import com.nuvio.app.features.addons.httpGetText
|
import com.nuvio.app.features.addons.httpGetText
|
||||||
import com.nuvio.app.features.profiles.ProfileRepository
|
import com.nuvio.app.features.profiles.ProfileRepository
|
||||||
|
import com.nuvio.app.features.tmdb.TmdbService
|
||||||
import io.github.jan.supabase.postgrest.postgrest
|
import io.github.jan.supabase.postgrest.postgrest
|
||||||
import io.github.jan.supabase.postgrest.query.Order
|
import io.github.jan.supabase.postgrest.query.Order
|
||||||
import io.github.jan.supabase.postgrest.rpc
|
import io.github.jan.supabase.postgrest.rpc
|
||||||
|
|
@ -314,10 +315,15 @@ actual object PluginRepository {
|
||||||
season: Int?,
|
season: Int?,
|
||||||
episode: Int?,
|
episode: Int?,
|
||||||
): Result<List<PluginRuntimeResult>> {
|
): Result<List<PluginRuntimeResult>> {
|
||||||
|
val resolvedTmdbId = resolvePluginTmdbId(
|
||||||
|
tmdbId = tmdbId,
|
||||||
|
mediaType = mediaType,
|
||||||
|
)
|
||||||
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
PluginRuntime.executePlugin(
|
PluginRuntime.executePlugin(
|
||||||
code = scraper.code,
|
code = scraper.code,
|
||||||
tmdbId = tmdbId,
|
tmdbId = resolvedTmdbId,
|
||||||
mediaType = normalizePluginType(mediaType),
|
mediaType = normalizePluginType(mediaType),
|
||||||
season = season,
|
season = season,
|
||||||
episode = episode,
|
episode = episode,
|
||||||
|
|
@ -327,6 +333,19 @@ actual object PluginRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun resolvePluginTmdbId(
|
||||||
|
tmdbId: String,
|
||||||
|
mediaType: String,
|
||||||
|
): String {
|
||||||
|
val trimmed = tmdbId.trim()
|
||||||
|
if (trimmed.isBlank()) return tmdbId
|
||||||
|
|
||||||
|
return TmdbService.ensureTmdbId(
|
||||||
|
videoId = trimmed,
|
||||||
|
mediaType = mediaType,
|
||||||
|
) ?: trimmed
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun fetchRepositoryData(
|
private suspend fun fetchRepositoryData(
|
||||||
manifestUrl: String,
|
manifestUrl: String,
|
||||||
previousScrapers: Map<String, PluginScraper>,
|
previousScrapers: Map<String, PluginScraper>,
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import com.nuvio.app.core.ui.NuvioInputField
|
||||||
import com.nuvio.app.core.ui.NuvioPrimaryButton
|
import com.nuvio.app.core.ui.NuvioPrimaryButton
|
||||||
import com.nuvio.app.core.ui.NuvioSectionLabel
|
import com.nuvio.app.core.ui.NuvioSectionLabel
|
||||||
import com.nuvio.app.core.ui.NuvioSurfaceCard
|
import com.nuvio.app.core.ui.NuvioSurfaceCard
|
||||||
|
import com.nuvio.app.features.tmdb.TmdbSettingsRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -49,6 +50,10 @@ fun PluginsSettingsPageContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
val uiState by PluginRepository.uiState.collectAsStateWithLifecycle()
|
val uiState by PluginRepository.uiState.collectAsStateWithLifecycle()
|
||||||
|
val tmdbSettings by remember {
|
||||||
|
TmdbSettingsRepository.ensureLoaded()
|
||||||
|
TmdbSettingsRepository.uiState
|
||||||
|
}.collectAsStateWithLifecycle()
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
var repositoryUrl by rememberSaveable { mutableStateOf("") }
|
var repositoryUrl by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
@ -61,6 +66,7 @@ fun PluginsSettingsPageContent(
|
||||||
val sortedRepos = remember(uiState.repositories) {
|
val sortedRepos = remember(uiState.repositories) {
|
||||||
uiState.repositories.sortedBy { it.name.lowercase() }
|
uiState.repositories.sortedBy { it.name.lowercase() }
|
||||||
}
|
}
|
||||||
|
val hasTmdbApiKey = tmdbSettings.hasApiKey
|
||||||
val repositoryNameByUrl = remember(sortedRepos) {
|
val repositoryNameByUrl = remember(sortedRepos) {
|
||||||
sortedRepos.associate { it.manifestUrl to it.name }
|
sortedRepos.associate { it.manifestUrl to it.name }
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +94,17 @@ fun PluginsSettingsPageContent(
|
||||||
NuvioInfoBadge(
|
NuvioInfoBadge(
|
||||||
text = if (uiState.pluginsEnabled) "Plugins enabled" else "Plugins disabled",
|
text = if (uiState.pluginsEnabled) "Plugins enabled" else "Plugins disabled",
|
||||||
)
|
)
|
||||||
|
NuvioInfoBadge(
|
||||||
|
text = if (hasTmdbApiKey) "TMDB API key set" else "TMDB API key missing",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!hasTmdbApiKey) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
text = "Plugin providers require a TMDB API key. Set it on the TMDB screen or plugin providers will not work correctly.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -355,7 +372,7 @@ fun PluginsSettingsPageContent(
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
NuvioPrimaryButton(
|
NuvioPrimaryButton(
|
||||||
text = if (isTestingThisScraper) "Testing..." else "Test Provider",
|
text = if (isTestingThisScraper) "Testing..." else "Test Provider",
|
||||||
enabled = !isTestingThisScraper,
|
enabled = hasTmdbApiKey && !isTestingThisScraper,
|
||||||
onClick = {
|
onClick = {
|
||||||
testingScraperId = scraper.id
|
testingScraperId = scraper.id
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue