ref: adjust url building to be in parity with addon protocol

This commit is contained in:
tapframe 2026-04-19 02:58:25 +05:30
parent ab0fc039b9
commit bd84bd9b56
6 changed files with 82 additions and 29 deletions

View file

@ -0,0 +1,46 @@
package com.nuvio.app.features.addons
internal fun addonTransportBaseUrl(manifestUrl: String): String =
manifestUrl.substringBefore("?").removeSuffix("/manifest.json")
internal fun buildAddonResourceUrl(
manifestUrl: String,
resource: String,
type: String,
id: String,
extraPathSegment: String? = null,
): String {
val encodedId = id.encodeAddonPathSegment()
val baseUrl = addonTransportBaseUrl(manifestUrl)
return if (extraPathSegment.isNullOrEmpty()) {
"$baseUrl/$resource/$type/$encodedId.json"
} else {
"$baseUrl/$resource/$type/$encodedId/$extraPathSegment.json"
}
}
internal fun String.encodeAddonPathSegment(): String =
buildString {
encodeToByteArray().forEach { byte ->
val value = byte.toInt() and 0xFF
val char = value.toChar()
if (
char in 'a'..'z' ||
char in 'A'..'Z' ||
char in '0'..'9' ||
char == '-' ||
char == '_' ||
char == '.' ||
char == '~'
) {
append(char)
} else {
append('%')
append(ADDON_URL_HEX[value shr 4])
append(ADDON_URL_HEX[value and 0x0F])
}
}
}
private const val ADDON_URL_HEX = "0123456789ABCDEF"

View file

@ -1,6 +1,7 @@
package com.nuvio.app.features.catalog package com.nuvio.app.features.catalog
import com.nuvio.app.features.addons.AddonCatalog import com.nuvio.app.features.addons.AddonCatalog
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.home.HomeCatalogParser import com.nuvio.app.features.home.HomeCatalogParser
import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.MetaPreview
@ -122,21 +123,19 @@ internal fun buildCatalogUrl(
search: String?, search: String?,
skip: Int?, skip: Int?,
): String { ): String {
val baseUrl = manifestUrl
.substringBefore("?")
.removeSuffix("/manifest.json")
val extraParts = buildList { val extraParts = buildList {
if (!search.isNullOrBlank()) add("search=${search.encodeCatalogExtra()}") if (!search.isNullOrBlank()) add("search=${search.encodeCatalogExtra()}")
if (!genre.isNullOrBlank()) add("genre=${genre.encodeCatalogExtra()}") if (!genre.isNullOrBlank()) add("genre=${genre.encodeCatalogExtra()}")
if (skip != null && skip > 0) add("skip=$skip") if (skip != null && skip > 0) add("skip=$skip")
} }
return if (extraParts.isEmpty()) { return buildAddonResourceUrl(
"$baseUrl/catalog/$type/$catalogId.json" manifestUrl = manifestUrl,
} else { resource = "catalog",
"$baseUrl/catalog/$type/$catalogId/${extraParts.joinToString(separator = "&")}.json" type = type,
} id = catalogId,
extraPathSegment = extraParts.joinToString(separator = "&").ifBlank { null },
)
} }
private fun String.encodeCatalogExtra(): String = private fun String.encodeCatalogExtra(): String =

View file

@ -3,6 +3,7 @@ package com.nuvio.app.features.details
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.nuvio.app.features.addons.AddonManifest import com.nuvio.app.features.addons.AddonManifest
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
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.mdblist.MdbListMetadataService import com.nuvio.app.features.mdblist.MdbListMetadataService
import com.nuvio.app.features.mdblist.MdbListSettingsRepository import com.nuvio.app.features.mdblist.MdbListSettingsRepository
@ -217,10 +218,12 @@ object MetaDetailsRepository {
id: String, id: String,
includeMdbList: Boolean, includeMdbList: Boolean,
): MetaDetails? { ): MetaDetails? {
val baseUrl = manifest.transportUrl val url = buildAddonResourceUrl(
.substringBefore("?") manifestUrl = manifest.transportUrl,
.removeSuffix("/manifest.json") resource = "meta",
val url = "$baseUrl/meta/$type/$id.json" type = type,
id = id,
)
return try { return try {
TmdbSettingsRepository.ensureLoaded() TmdbSettingsRepository.ensureLoaded()

View file

@ -3,6 +3,7 @@ package com.nuvio.app.features.player
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.build.AppFeaturePolicy
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
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
@ -215,11 +216,12 @@ object PlayerStreamsRepository {
val job = scope.launch { val job = scope.launch {
val addonJobs = streamAddons.map { addon -> val addonJobs = streamAddons.map { addon ->
async { async {
val encodedId = videoId.replace("%", "%25").replace(" ", "%20") val url = buildAddonResourceUrl(
val baseUrl = addon.manifest.transportUrl manifestUrl = addon.manifest.transportUrl,
.substringBefore("?") resource = "stream",
.removeSuffix("/manifest.json") type = type,
val url = "$baseUrl/stream/$type/$encodedId.json" id = videoId,
)
val displayName = addon.addonName val displayName = addon.addonName
runCatching { runCatching {

View file

@ -1,6 +1,7 @@
package com.nuvio.app.features.player package com.nuvio.app.features.player
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.addons.buildAddonResourceUrl
import com.nuvio.app.features.addons.httpGetText import com.nuvio.app.features.addons.httpGetText
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -49,8 +50,12 @@ object SubtitleRepository {
subtitleResource.idPrefixes.any { videoId.startsWith(it) } subtitleResource.idPrefixes.any { videoId.startsWith(it) }
if (!prefixMatch) continue if (!prefixMatch) continue
val baseUrl = manifest.transportUrl.substringBeforeLast("/manifest.json") val subtitleUrl = buildAddonResourceUrl(
val subtitleUrl = "$baseUrl/subtitles/$type/$videoId.json" manifestUrl = manifest.transportUrl,
resource = "subtitles",
type = type,
id = videoId,
)
try { try {
val response = withContext(Dispatchers.Default) { val response = withContext(Dispatchers.Default) {

View file

@ -3,6 +3,7 @@ package com.nuvio.app.features.streams
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.build.AppFeaturePolicy
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
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.player.PlayerSettingsRepository import com.nuvio.app.features.player.PlayerSettingsRepository
@ -237,11 +238,12 @@ object StreamsRepository {
streamAddons.forEach { addon -> streamAddons.forEach { addon ->
launch { launch {
val encodedId = videoId.encodeForPath() val url = buildAddonResourceUrl(
val baseUrl = addon.manifest.transportUrl manifestUrl = addon.manifest.transportUrl,
.substringBefore("?") resource = "stream",
.removeSuffix("/manifest.json") type = type,
val url = "$baseUrl/stream/$type/$encodedId.json" id = videoId,
)
log.d { "Fetching streams from: $url" } log.d { "Fetching streams from: $url" }
val displayName = addon.addonName val displayName = addon.addonName
@ -420,10 +422,6 @@ object StreamsRepository {
activeRequestKey = null activeRequestKey = null
_uiState.value = StreamsUiState() _uiState.value = StreamsUiState()
} }
// Encode id segment so colons and slashes don't break URL path parsing on addons
private fun String.encodeForPath(): String =
replace("%", "%25").replace(" ", "%20")
} }
private data class InstalledStreamAddonTarget( private data class InstalledStreamAddonTarget(