mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-09 00:17:59 +00:00
fix(simkl): parse resolved history mutations
This commit is contained in:
parent
b0aa543f26
commit
942a111eec
3 changed files with 114 additions and 12 deletions
|
|
@ -10,6 +10,7 @@ import com.nuvio.app.features.tracking.TrackingListWriter
|
|||
import com.nuvio.app.features.tracking.TrackingMediaKind
|
||||
import com.nuvio.app.features.tracking.TrackingMediaReference
|
||||
import com.nuvio.app.features.tracking.TrackingMutationResult
|
||||
import com.nuvio.app.features.tracking.TrackingMutationResolution
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.TrackingProviderRegistry
|
||||
import com.nuvio.app.features.tracking.TrackingRefreshIntent
|
||||
|
|
@ -378,25 +379,65 @@ private fun SimklApiResponse.toMutationResult(attemptedCount: Int, json: Json):
|
|||
?.values
|
||||
?.sumOf { value -> (value as? JsonArray)?.size ?: 0 }
|
||||
?: 0
|
||||
val resolvedListStatuses = payload
|
||||
val added = payload
|
||||
?.get("added")
|
||||
?.let { value -> runCatching { value.jsonObject }.getOrNull() }
|
||||
?.values
|
||||
.orEmpty()
|
||||
.flatMap { value -> (value as? JsonArray).orEmpty() }
|
||||
.mapNotNull { value ->
|
||||
val wireValue = runCatching {
|
||||
value.jsonObject["to"]?.jsonPrimitive?.content
|
||||
}.getOrNull()
|
||||
TrackingListStatus.fromWireValue(wireValue)
|
||||
}
|
||||
val resolutions = added?.toMutationResolutions().orEmpty()
|
||||
return TrackingMutationResult(
|
||||
attemptedCount = attemptedCount,
|
||||
notFoundCount = notFoundCount,
|
||||
resolvedListStatuses = resolvedListStatuses,
|
||||
resolutions = resolutions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.toMutationResolutions(): List<TrackingMutationResolution> {
|
||||
val historyResolutions = (get("statuses") as? JsonArray)
|
||||
.orEmpty()
|
||||
.mapNotNull { element ->
|
||||
val response = runCatching { element.jsonObject["response"]?.jsonObject }.getOrNull()
|
||||
?: return@mapNotNull null
|
||||
response.toMutationResolution(statusKey = "status")
|
||||
}
|
||||
if (historyResolutions.isNotEmpty()) return historyResolutions
|
||||
|
||||
return entries.flatMap { (bucket, value) ->
|
||||
(value as? JsonArray).orEmpty().mapNotNull { element ->
|
||||
runCatching { element.jsonObject }.getOrNull()
|
||||
?.toMutationResolution(statusKey = "to", fallbackKind = bucket.toTrackingMediaKind())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonObject.toMutationResolution(
|
||||
statusKey: String,
|
||||
fallbackKind: TrackingMediaKind? = null,
|
||||
): TrackingMutationResolution? {
|
||||
val status = TrackingListStatus.fromWireValue(stringValue(statusKey))
|
||||
val mediaKind = stringValue("simkl_type")?.toTrackingMediaKind()
|
||||
?: stringValue("type")?.toTrackingMediaKind()
|
||||
?: fallbackKind
|
||||
val providerSubtype = stringValue("anime_type")
|
||||
if (status == null && mediaKind == null && providerSubtype == null) return null
|
||||
return TrackingMutationResolution(
|
||||
listStatus = status,
|
||||
mediaKind = mediaKind,
|
||||
providerSubtype = providerSubtype,
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.stringValue(key: String): String? =
|
||||
runCatching { get(key)?.jsonPrimitive?.content }
|
||||
.getOrNull()
|
||||
?.trim()
|
||||
?.takeIf(String::isNotEmpty)
|
||||
|
||||
private fun String.toTrackingMediaKind(): TrackingMediaKind? = when (lowercase()) {
|
||||
"movie", "movies" -> TrackingMediaKind.MOVIE
|
||||
"anime" -> TrackingMediaKind.ANIME
|
||||
"tv", "show", "shows" -> TrackingMediaKind.SHOW
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun Double.clampAndRoundProgress(): Double = round(coerceIn(0.0, 100.0) * 100.0) / 100.0
|
||||
|
||||
private fun String?.nonBlankOrNull(): String? = this?.trim()?.takeIf(String::isNotEmpty)
|
||||
|
|
|
|||
|
|
@ -32,12 +32,21 @@ data class TrackingScrobbleEvent(
|
|||
data class TrackingMutationResult(
|
||||
val attemptedCount: Int,
|
||||
val notFoundCount: Int = 0,
|
||||
val resolvedListStatuses: List<TrackingListStatus> = emptyList(),
|
||||
val resolutions: List<TrackingMutationResolution> = emptyList(),
|
||||
) {
|
||||
val resolvedListStatuses: List<TrackingListStatus>
|
||||
get() = resolutions.mapNotNull(TrackingMutationResolution::listStatus)
|
||||
|
||||
val isComplete: Boolean
|
||||
get() = notFoundCount == 0
|
||||
}
|
||||
|
||||
data class TrackingMutationResolution(
|
||||
val listStatus: TrackingListStatus? = null,
|
||||
val mediaKind: TrackingMediaKind? = null,
|
||||
val providerSubtype: String? = null,
|
||||
)
|
||||
|
||||
interface TrackingListWriter {
|
||||
val providerId: TrackingProviderId
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,58 @@ class SimklMutationRepositoryTest {
|
|||
assertEquals(2, committed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `history response exposes resolved status catalog and anime subtype`() = runBlocking {
|
||||
val engine = RecordingEngine(
|
||||
response(
|
||||
status = 201,
|
||||
body = """
|
||||
{
|
||||
"added": {
|
||||
"movies": 0,
|
||||
"shows": 1,
|
||||
"episodes": 1,
|
||||
"statuses": [
|
||||
{
|
||||
"request": {"title":"Attack on Titan","type":"show"},
|
||||
"response": {
|
||||
"status":"watching",
|
||||
"simkl_type":"anime",
|
||||
"anime_type":"tv"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"not_found": {"movies":[],"shows":[],"episodes":[]}
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
val service = SimklMutationService(
|
||||
client = SimklApiClient(
|
||||
engine = engine,
|
||||
accessToken = { "token" },
|
||||
onUnauthorized = {},
|
||||
nowEpochMs = { 0L },
|
||||
sleep = {},
|
||||
retryJitterMs = { 0L },
|
||||
),
|
||||
)
|
||||
|
||||
val result = service.addToHistory(
|
||||
listOf(
|
||||
TrackingHistoryItem(
|
||||
media = anime(TrackingEpisode(number = 1)),
|
||||
watchedAtEpochMs = 1_700_000_000_000L,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(listOf(TrackingListStatus.WATCHING), result.resolvedListStatuses)
|
||||
assertEquals(TrackingMediaKind.ANIME, result.resolutions.single().mediaKind)
|
||||
assertEquals("tv", result.resolutions.single().providerSubtype)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `service leaves failed scrobble retry to the next player event`() = runBlocking {
|
||||
val engine = RecordingEngine(response(status = 503), response(status = 200))
|
||||
|
|
|
|||
Loading…
Reference in a new issue