Add the pinned Conductarr TV client and disk cache
All checks were successful
review/code-claude Claude cross-family review: clean, no blocking findings
All checks were successful
review/code-claude Claude cross-family review: clean, no blocking findings
This commit is contained in:
parent
de452448ba
commit
adbfe5d0e7
87 changed files with 21924 additions and 0 deletions
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
/** Caller lifecycle events; no background poller or UI is owned by the client. */
|
||||
enum class ReadTrigger { IF_STALE, LAUNCH, FOREGROUND, OPEN, POLL, FORCE, CACHE_ONLY }
|
||||
|
||||
/** Refresh lifetimes do not expire last-known-good data or delete it on failure. */
|
||||
enum class CachePolicy(internal val lifetimeMillis: Long, internal val capacity: Int? = null) {
|
||||
LAUNCH(Long.MAX_VALUE),
|
||||
DAILY(24 * 60 * 60 * 1000L),
|
||||
FEED(0),
|
||||
FOREGROUND(Long.MAX_VALUE),
|
||||
DISCOVERY(30 * 60 * 1000L),
|
||||
RATINGS(6 * 60 * 60 * 1000L, 300),
|
||||
ON_OPEN(Long.MAX_VALUE),
|
||||
SEASON(Long.MAX_VALUE, 300),
|
||||
ACTIVITY(30 * 1000L),
|
||||
NO_CACHE(0),
|
||||
;
|
||||
|
||||
internal fun shouldRefresh(trigger: ReadTrigger, entry: CacheEntry?, now: Long): Boolean {
|
||||
if (trigger == ReadTrigger.CACHE_ONLY) return false
|
||||
if (entry == null || trigger == ReadTrigger.FORCE) return true
|
||||
if (this == LAUNCH && trigger == ReadTrigger.LAUNCH) return true
|
||||
if ((this == FOREGROUND || this == ACTIVITY) && trigger == ReadTrigger.FOREGROUND) return true
|
||||
if ((this == ON_OPEN || this == SEASON) && trigger == ReadTrigger.OPEN) return true
|
||||
val expiry = entry.expiresAtMillis
|
||||
return if (this == FEED) expiry == null || now >= expiry
|
||||
else lifetimeMillis != Long.MAX_VALUE && now - entry.validatedAtMillis >= lifetimeMillis
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** A validated response; saved time is preserved across ETag revalidation. */
|
||||
@Serializable
|
||||
data class CacheEntry(
|
||||
val body: String,
|
||||
val etag: String?,
|
||||
val savedAtMillis: Long,
|
||||
val validatedAtMillis: Long,
|
||||
val expiresAtMillis: Long? = null,
|
||||
val absent: Boolean = false,
|
||||
val group: String,
|
||||
)
|
||||
|
||||
/** Storage is partitioned by server and credential digest; implementations never receive tokens. */
|
||||
interface CacheStore {
|
||||
fun read(scope: String, key: String): CacheEntry?
|
||||
fun write(scope: String, key: String, entry: CacheEntry, capacity: Int? = null)
|
||||
fun remove(scope: String, key: String)
|
||||
fun clear(scope: String)
|
||||
}
|
||||
|
||||
/** Deterministic fake with the same group capacity semantics as the disk store. */
|
||||
class MemoryCacheStore : CacheStore {
|
||||
private val entries = mutableMapOf<Pair<String, String>, CacheEntry>()
|
||||
|
||||
@Synchronized override fun read(scope: String, key: String): CacheEntry? = entries[scope to key]
|
||||
|
||||
@Synchronized override fun write(scope: String, key: String, entry: CacheEntry, capacity: Int?) {
|
||||
entries[scope to key] = entry
|
||||
if (capacity != null) {
|
||||
entries.filter { it.key.first == scope && it.value.group == entry.group }
|
||||
.entries.sortedByDescending { it.value.validatedAtMillis }
|
||||
.drop(capacity).forEach { entries.remove(it.key) }
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized override fun remove(scope: String, key: String) { entries.remove(scope to key) }
|
||||
@Synchronized override fun clear(scope: String) { entries.keys.removeAll { it.first == scope } }
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
|
||||
/** Failures contain no response bodies, credentials or transport exception text. */
|
||||
sealed interface ClientError {
|
||||
data object NeedsSignIn : ClientError
|
||||
data class Http(val status: Int) : ClientError
|
||||
data object Unreachable : ClientError
|
||||
data object InvalidResponse : ClientError
|
||||
}
|
||||
|
||||
/** A successful copy includes the time needed by the Saved HH:MM indicator. */
|
||||
data class Saved<T>(val value: T?, val savedAtMillis: Long, val fromDisk: Boolean, val absent: Boolean = false)
|
||||
|
||||
/** Saved arrives before network work; a refresh failure retains it except on 401. */
|
||||
sealed interface ReadResult<out T> {
|
||||
data class Value<T>(val saved: Saved<T>) : ReadResult<T>
|
||||
data class Failure<T>(val error: ClientError, val saved: Saved<T>? = null) : ReadResult<T>
|
||||
}
|
||||
|
||||
/** Mutation results deliberately cannot contain a cached receipt. */
|
||||
sealed interface MutationResult<out T> {
|
||||
data class Success<T>(val value: T) : MutationResult<T>
|
||||
data class Failure(val error: ClientError) : MutationResult<Nothing>
|
||||
}
|
||||
|
||||
/** Safe diagnostic vocabulary: there is no string sink that could receive a token. */
|
||||
enum class ClientEvent { REFRESHED, REVALIDATED, NEEDS_SIGN_IN, HTTP_FAILURE, UNREACHABLE, INVALID_RESPONSE, CACHE_UNAVAILABLE }
|
||||
|
||||
/** Time limits apply to the entire call as well as connection and response I/O. */
|
||||
data class ClientTimeouts(val connectSeconds: Long = 10, val readSeconds: Long = 20, val callSeconds: Long = 30)
|
||||
|
||||
/** An immutable identity-scoped client; create another client when the selected person changes. */
|
||||
class ConductarrClient(
|
||||
baseUrl: String,
|
||||
private val jellyfinToken: String,
|
||||
private val cache: CacheStore,
|
||||
timeouts: ClientTimeouts = ClientTimeouts(),
|
||||
private val nowMillis: () -> Long = System::currentTimeMillis,
|
||||
private val onEvent: (ClientEvent) -> Unit = {},
|
||||
) : Closeable {
|
||||
private val base = baseUrl.toHttpUrl()
|
||||
private val scope: String
|
||||
private val revoked = AtomicBoolean(false)
|
||||
private val cacheIdentityLock = Any()
|
||||
private val catalogueReads = Semaphore(2)
|
||||
private val http = OkHttpClient.Builder()
|
||||
.connectTimeout(timeouts.connectSeconds, TimeUnit.SECONDS)
|
||||
.readTimeout(timeouts.readSeconds, TimeUnit.SECONDS)
|
||||
.callTimeout(timeouts.callSeconds, TimeUnit.SECONDS)
|
||||
.followRedirects(false)
|
||||
.followSslRedirects(false)
|
||||
.retryOnConnectionFailure(false)
|
||||
.build()
|
||||
|
||||
init {
|
||||
require(base.username.isEmpty() && base.password.isEmpty() && base.query == null && base.fragment == null) { "Base URL must not contain credentials, query or fragment" }
|
||||
require(base.isHttps || base.host in setOf("localhost", "127.0.0.1", "::1")) { "Conductarr requires HTTPS" }
|
||||
require(jellyfinToken.matches(Regex("[0-9a-fA-F]{32}"))) { "Expected a Jellyfin access token" }
|
||||
scope = digest("$base\n$jellyfinToken")
|
||||
}
|
||||
|
||||
/** Emit disk LKG first, then revalidate when the endpoint lifetime or caller event requires it. */
|
||||
fun <T> read(call: ReadCall<T>, trigger: ReadTrigger = ReadTrigger.IF_STALE): Flow<ReadResult<T>> = flow {
|
||||
if (revoked.get()) {
|
||||
emit(ReadResult.Failure(ClientError.NeedsSignIn))
|
||||
return@flow
|
||||
}
|
||||
val url = url(call.contract, call.segments, call.query)
|
||||
val group = call.contract.path + if (call.policy == CachePolicy.RATINGS) "-${call.segments["type"]}" else ""
|
||||
val key = "$group:${digest(url.toString())}"
|
||||
val entry = if (call.policy != CachePolicy.NO_CACHE) cacheRead(key) else null
|
||||
val saved = entry?.let { decodeSaved(call.serializer, it, true) }
|
||||
if (saved != null) emit(ReadResult.Value(saved))
|
||||
if (!call.policy.shouldRefresh(trigger, if (saved != null) entry else null, nowMillis())) return@flow
|
||||
val request = request(url).apply { if (saved != null) entry.etag?.let { header("If-None-Match", it) } }.build()
|
||||
val result = if (call.contract.path.startsWith("/catalogue/")) {
|
||||
catalogueReads.withPermit { refresh(call, request, key, group, entry.takeIf { saved != null }, saved) }
|
||||
} else refresh(call, request, key, group, entry.takeIf { saved != null }, saved)
|
||||
emit(result)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
/** Execute once; an uncertain request outcome must be checked with the title-requests read. */
|
||||
suspend fun <T> mutate(call: MutationCall<T>): MutationResult<T> = withContext(Dispatchers.IO) {
|
||||
if (revoked.get()) return@withContext MutationResult.Failure(ClientError.NeedsSignIn)
|
||||
val body = call.body?.toRequestBody("application/json".toMediaType())
|
||||
val request = request(url(call.contract, call.segments, emptyMap())).method(call.contract.method, body).build()
|
||||
try {
|
||||
http.newCall(request).awaitResponse().use { response ->
|
||||
if (response.code == 401) return@withContext MutationResult.Failure(revoke())
|
||||
if (!response.isSuccessful) return@withContext MutationResult.Failure(ClientError.Http(response.code))
|
||||
val value = WireJson.decodeFromString(call.serializer, response.body.string())
|
||||
if (revoked.get()) MutationResult.Failure(ClientError.NeedsSignIn)
|
||||
else MutationResult.Success(value)
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
onEvent(ClientEvent.UNREACHABLE)
|
||||
MutationResult.Failure(ClientError.Unreachable)
|
||||
} catch (_: SerializationException) {
|
||||
onEvent(ClientEvent.INVALID_RESPONSE)
|
||||
MutationResult.Failure(ClientError.InvalidResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> refresh(call: ReadCall<T>, request: Request, key: String, group: String, entry: CacheEntry?, saved: Saved<T>?): ReadResult<T> {
|
||||
return try {
|
||||
http.newCall(request).awaitResponse().use { response ->
|
||||
if (response.code == 401) return ReadResult.Failure(revoke())
|
||||
if (revoked.get()) return ReadResult.Failure(ClientError.NeedsSignIn)
|
||||
if (response.code == 304) {
|
||||
if (entry == null || saved == null) return ReadResult.Failure(ClientError.InvalidResponse)
|
||||
cacheWrite(key, entry.copy(etag = response.header("ETag") ?: entry.etag, validatedAtMillis = nowMillis()), call.policy)
|
||||
onEvent(ClientEvent.REVALIDATED)
|
||||
return ReadResult.Value(saved.copy(fromDisk = false))
|
||||
}
|
||||
if (response.code == 404 && call.policy == CachePolicy.RATINGS) {
|
||||
val absent = CacheEntry("", null, nowMillis(), nowMillis(), absent = true, group = group)
|
||||
cacheWrite(key, absent, call.policy)
|
||||
return ReadResult.Value(Saved(null, absent.savedAtMillis, false, true))
|
||||
}
|
||||
if (!response.isSuccessful) {
|
||||
onEvent(ClientEvent.HTTP_FAILURE)
|
||||
return ReadResult.Failure(ClientError.Http(response.code), saved)
|
||||
}
|
||||
val body = response.body.string()
|
||||
val value = WireJson.decodeFromString(call.serializer, body)
|
||||
val timestamp = nowMillis()
|
||||
val updated = CacheEntry(body, response.header("ETag"), timestamp, timestamp, feedExpiry(call.policy, body), group = group)
|
||||
if (call.policy != CachePolicy.NO_CACHE) cacheWrite(key, updated, call.policy)
|
||||
onEvent(ClientEvent.REFRESHED)
|
||||
ReadResult.Value(Saved(value, timestamp, false))
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
onEvent(ClientEvent.UNREACHABLE)
|
||||
ReadResult.Failure(ClientError.Unreachable, saved)
|
||||
} catch (_: SerializationException) {
|
||||
onEvent(ClientEvent.INVALID_RESPONSE)
|
||||
ReadResult.Failure(ClientError.InvalidResponse, saved)
|
||||
}
|
||||
}
|
||||
|
||||
private fun request(url: HttpUrl): Request.Builder = Request.Builder().url(url)
|
||||
.header("Authorization", "MediaBrowser Token=\"$jellyfinToken\"")
|
||||
.header("Accept", "application/json")
|
||||
|
||||
private fun url(contract: RouteContract, segments: Map<String, String>, query: Map<String, String>): HttpUrl {
|
||||
val builder = base.newBuilder()
|
||||
for (segment in contract.path.trimStart('/').split('/')) {
|
||||
builder.addPathSegment(if (segment.startsWith('{')) segments.getValue(segment.removeSurrounding("{", "}")) else segment)
|
||||
}
|
||||
query.toSortedMap().forEach { (name, value) -> builder.addQueryParameter(name, value) }
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun <T> decodeSaved(serializer: KSerializer<T>, entry: CacheEntry, fromDisk: Boolean): Saved<T>? = try {
|
||||
Saved(if (entry.absent) null else WireJson.decodeFromString(serializer, entry.body), entry.savedAtMillis, fromDisk, entry.absent)
|
||||
} catch (_: SerializationException) { null }
|
||||
|
||||
private fun cacheRead(key: String): CacheEntry? = try { cache.read(scope, key) } catch (_: IOException) {
|
||||
onEvent(ClientEvent.CACHE_UNAVAILABLE)
|
||||
null
|
||||
}
|
||||
|
||||
private fun cacheWrite(key: String, entry: CacheEntry, policy: CachePolicy) = synchronized(cacheIdentityLock) {
|
||||
if (!revoked.get()) {
|
||||
try { cache.write(scope, key, entry, policy.capacity) } catch (_: IOException) { onEvent(ClientEvent.CACHE_UNAVAILABLE) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun revoke(): ClientError {
|
||||
synchronized(cacheIdentityLock) {
|
||||
revoked.set(true)
|
||||
try { cache.clear(scope) } catch (_: IOException) { onEvent(ClientEvent.CACHE_UNAVAILABLE) }
|
||||
}
|
||||
onEvent(ClientEvent.NEEDS_SIGN_IN)
|
||||
return ClientError.NeedsSignIn
|
||||
}
|
||||
|
||||
private fun feedExpiry(policy: CachePolicy, body: String): Long? {
|
||||
if (policy != CachePolicy.FEED) return null
|
||||
val expiry = WireJson.parseToJsonElement(body).jsonObject["expires_at"]?.jsonPrimitive?.content ?: return null
|
||||
return try { OffsetDateTime.parse(expiry).toInstant().toEpochMilli() } catch (_: DateTimeParseException) { null }
|
||||
}
|
||||
|
||||
/** Release this instance's network resources; disk LKG survives. */
|
||||
override fun close() {
|
||||
http.dispatcher.cancelAll()
|
||||
http.dispatcher.executorService.shutdown()
|
||||
http.connectionPool.evictAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Call.awaitResponse(): Response = suspendCancellableCoroutine { continuation ->
|
||||
continuation.invokeOnCancellation { cancel() }
|
||||
enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
if (!continuation.isCancelled) continuation.resumeWithException(e)
|
||||
}
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
continuation.resume(response) { _, value, _ -> value.close() }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.security.MessageDigest
|
||||
import kotlinx.serialization.SerializationException
|
||||
|
||||
/** App-private disk cache, safe to reopen after process death; one store instance per directory. */
|
||||
class FileCacheStore(private val directory: File) : CacheStore {
|
||||
@Synchronized override fun read(scope: String, key: String): CacheEntry? {
|
||||
val file = entryFile(scope, key)
|
||||
if (!file.exists()) return null
|
||||
return try {
|
||||
WireJson.decodeFromString<CacheEntry>(file.readText())
|
||||
} catch (_: SerializationException) {
|
||||
file.delete()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized override fun write(scope: String, key: String, entry: CacheEntry, capacity: Int?) {
|
||||
val target = entryFile(scope, key)
|
||||
val parent = target.parentFile!!
|
||||
if (!parent.isDirectory && !parent.mkdirs()) throw IOException("Cache directory unavailable")
|
||||
val temporary = File.createTempFile("entry-", ".tmp", parent)
|
||||
try {
|
||||
temporary.outputStream().use { output ->
|
||||
output.write(WireJson.encodeToString(CacheEntry.serializer(), entry).toByteArray(Charsets.UTF_8))
|
||||
output.fd.sync()
|
||||
}
|
||||
if (!temporary.renameTo(target)) throw IOException("Cache replacement failed")
|
||||
target.setLastModified(entry.validatedAtMillis)
|
||||
} finally {
|
||||
temporary.delete()
|
||||
}
|
||||
if (capacity != null) {
|
||||
parent.listFiles { file -> file.extension == "json" }
|
||||
?.sortedByDescending { it.lastModified() }?.drop(capacity)?.forEach { it.delete() }
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized override fun remove(scope: String, key: String) { entryFile(scope, key).delete() }
|
||||
@Synchronized override fun clear(scope: String) { File(directory, digest(scope)).deleteRecursively() }
|
||||
|
||||
private fun entryFile(scope: String, key: String): File =
|
||||
File(File(File(directory, digest(scope)), digest(key.substringBefore(':'))), digest(key) + ".json")
|
||||
}
|
||||
|
||||
/** Opaque storage names never expose credentials, query parameters or household names. */
|
||||
internal fun digest(value: String): String {
|
||||
val bytes = MessageDigest.getInstance("SHA-256").digest(value.toByteArray(Charsets.UTF_8))
|
||||
val hex = "0123456789abcdef"
|
||||
val characters = CharArray(bytes.size * 2)
|
||||
bytes.forEachIndexed { index, byte ->
|
||||
val number = byte.toInt() and 0xff
|
||||
characters[index * 2] = hex[number ushr 4]
|
||||
characters[index * 2 + 1] = hex[number and 0x0f]
|
||||
}
|
||||
return String(characters)
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import org.justinvisser.wolfskin.conductarr.model.*
|
||||
|
||||
/** A TV-used operation and its pinned request/response contract. */
|
||||
class RouteContract internal constructor(
|
||||
val method: String,
|
||||
val path: String,
|
||||
val responseSchema: String,
|
||||
val requestSchema: String? = null,
|
||||
)
|
||||
|
||||
/** A typed read; only TvRoutes can construct paths and policies. */
|
||||
class ReadCall<T> internal constructor(
|
||||
internal val contract: RouteContract,
|
||||
internal val serializer: KSerializer<T>,
|
||||
internal val segments: Map<String, String> = emptyMap(),
|
||||
internal val query: Map<String, String> = emptyMap(),
|
||||
internal val policy: CachePolicy,
|
||||
)
|
||||
|
||||
/** A single mutation; failures are returned without automatic retry or replay. */
|
||||
class MutationCall<T> internal constructor(
|
||||
internal val contract: RouteContract,
|
||||
internal val serializer: KSerializer<T>,
|
||||
internal val segments: Map<String, String>,
|
||||
internal val body: String? = null,
|
||||
)
|
||||
|
||||
/** The catalogue media type accepted by TV operations. */
|
||||
enum class MediaType(internal val wire: String) { MOVIE("movie"), TV("tv") }
|
||||
|
||||
/** Feed filter; the unfiltered feed uses all. */
|
||||
enum class MediaFilter(internal val wire: String) { ALL("all"), MOVIE("movie"), TV("tv") }
|
||||
|
||||
/** The four discovery sorts used by the TV. */
|
||||
enum class DiscoverSort(internal val wire: String) { POPULAR("popular"), NEW("new"), TOP("top"), UPCOMING("upcoming") }
|
||||
|
||||
/** The complete TV route allowlist; caller identity is never a request body field. */
|
||||
object TvRoutes {
|
||||
private val operator = RouteContract("GET", "/api/v1/operator", "Operator")
|
||||
private val profiles = RouteContract("GET", "/api/v1/recommendations/profiles", "RecommendationProfiles")
|
||||
private val feed = RouteContract("GET", "/api/v1/recommendations/profiles/{profile}/feed", "RecommendationFeed")
|
||||
private val dismissals = RouteContract("GET", "/api/v1/recommendations/profiles/{profile}/dismissals", "RecommendationDismissals")
|
||||
private val hide = RouteContract("POST", dismissals.path, "RecommendationHideResult", "RecommendationHideInputBody")
|
||||
private val restore = RouteContract("DELETE", "${dismissals.path}/{type}/{tmdb}", "RecommendationRestoreResult")
|
||||
private val options = RouteContract("GET", "/catalogue/options", "Options")
|
||||
private val discover = RouteContract("GET", "/catalogue/discover", "Page")
|
||||
private val title = RouteContract("GET", "/catalogue/titles/{tmdb}/{type}", "Detail")
|
||||
private val cast = RouteContract("GET", "${title.path}/cast", "TitleCast")
|
||||
private val related = RouteContract("GET", "${title.path}/related", "Page")
|
||||
private val enrichment = RouteContract("GET", "${title.path}/enrichment", "Enrichment")
|
||||
private val season = RouteContract("GET", "/catalogue/tv/{tmdb}/seasons/{season}", "SeasonDetail")
|
||||
private val performer = RouteContract("GET", "/catalogue/performers/{performer}", "PerformerDetail")
|
||||
private val filmography = RouteContract("GET", "${performer.path}/filmography", "FilmographyPage")
|
||||
private val activity = RouteContract("GET", "/api/v1/activity", "ActivityFeed")
|
||||
private val readiness = RouteContract("GET", "/api/v1/activity/titles/{tmdb}/{type}", "ViewerReadiness")
|
||||
private val requests = RouteContract("GET", "/api/v1/requests/titles/{tmdb}/{type}", "TitleRequestsBody")
|
||||
private val request = RouteContract("POST", "/api/v1/requests", "RequestReceipt", "RequestIntentBody")
|
||||
private val audiences = RouteContract("GET", "/curation/library-audiences", "LibraryAudiencesBody")
|
||||
|
||||
val contracts: List<RouteContract> = listOf(
|
||||
operator, profiles, feed, dismissals, hide, restore, options, discover, title, cast,
|
||||
related, enrichment, season, performer, filmography, activity, readiness, requests, request, audiences,
|
||||
)
|
||||
|
||||
fun operator() = ReadCall(operator, Operator.serializer(), policy = CachePolicy.LAUNCH)
|
||||
fun profiles() = ReadCall(profiles, RecommendationProfiles.serializer(), policy = CachePolicy.LAUNCH)
|
||||
fun audiences() = ReadCall(audiences, LibraryAudiencesBody.serializer(), policy = CachePolicy.DAILY)
|
||||
|
||||
fun feed(profile: String, media: MediaFilter = MediaFilter.ALL) = ReadCall(
|
||||
feed, RecommendationFeed.serializer(), mapOf("profile" to profile),
|
||||
mapOf("media_type" to media.wire), CachePolicy.FEED,
|
||||
)
|
||||
|
||||
fun dismissals(profile: String) = ReadCall(
|
||||
dismissals, RecommendationDismissals.serializer(), mapOf("profile" to profile), policy = CachePolicy.ON_OPEN,
|
||||
)
|
||||
|
||||
fun options(media: MediaType) = ReadCall(
|
||||
options, Options.serializer(), query = mapOf("media_type" to media.wire), policy = CachePolicy.FOREGROUND,
|
||||
)
|
||||
|
||||
fun discover(media: MediaType, page: Int = 1, genreId: Long? = null, serviceId: Long? = null, sort: DiscoverSort = DiscoverSort.POPULAR): ReadCall<Page> {
|
||||
require(page in 1..500)
|
||||
val query = buildMap {
|
||||
put("media_type", media.wire)
|
||||
put("page", page.toString())
|
||||
put("sort", sort.wire)
|
||||
genreId?.let { put("genre_id", it.toString()) }
|
||||
serviceId?.let { put("service_id", it.toString()) }
|
||||
}
|
||||
return ReadCall(discover, Page.serializer(), query = query, policy = CachePolicy.DISCOVERY)
|
||||
}
|
||||
|
||||
fun title(tmdb: Long, media: MediaType) = ReadCall(title, Detail.serializer(), titleSegments(tmdb, media), policy = CachePolicy.ON_OPEN)
|
||||
fun cast(tmdb: Long, media: MediaType) = ReadCall(cast, TitleCast.serializer(), titleSegments(tmdb, media), policy = CachePolicy.ON_OPEN)
|
||||
fun enrichment(tmdb: Long, media: MediaType) = ReadCall(enrichment, Enrichment.serializer(), titleSegments(tmdb, media), policy = CachePolicy.RATINGS)
|
||||
fun related(tmdb: Long, media: MediaType, page: Int = 1) = ReadCall(
|
||||
related, Page.serializer(), titleSegments(tmdb, media), mapOf("page" to page.toString()), CachePolicy.ON_OPEN,
|
||||
)
|
||||
fun season(tmdb: Long, season: Int): ReadCall<SeasonDetail> {
|
||||
require(tmdb > 0 && season >= 0)
|
||||
return ReadCall(this.season, SeasonDetail.serializer(), mapOf("tmdb" to tmdb.toString(), "season" to season.toString()), policy = CachePolicy.SEASON)
|
||||
}
|
||||
fun performer(performer: Long) = ReadCall(
|
||||
this.performer, PerformerDetail.serializer(), mapOf("performer" to performer.toString()), policy = CachePolicy.ON_OPEN,
|
||||
)
|
||||
fun filmography(performer: Long, page: Int = 1, pageSize: Int = 20) = ReadCall(
|
||||
filmography, FilmographyPage.serializer(), mapOf("performer" to performer.toString()),
|
||||
mapOf("page" to page.toString(), "page_size" to pageSize.toString()), CachePolicy.ON_OPEN,
|
||||
)
|
||||
fun activity() = ReadCall(activity, ActivityFeed.serializer(), query = mapOf("requester" to "all"), policy = CachePolicy.ACTIVITY)
|
||||
fun readiness(tmdb: Long, media: MediaType, viewer: String) = ReadCall(
|
||||
readiness, ViewerReadiness.serializer(), titleSegments(tmdb, media), mapOf("viewer" to viewer), CachePolicy.ON_OPEN,
|
||||
)
|
||||
fun requests(tmdb: Long, media: MediaType) = ReadCall(
|
||||
requests, TitleRequestsBody.serializer(), titleSegments(tmdb, media), policy = CachePolicy.NO_CACHE,
|
||||
)
|
||||
fun request(body: RequestIntentBody) = MutationCall(
|
||||
request, RequestReceipt.serializer(), emptyMap(), WireJson.encodeToString(RequestIntentBody.serializer(), body),
|
||||
)
|
||||
fun hide(profile: String, body: RecommendationHideInputBody) = MutationCall(
|
||||
hide, RecommendationHideResult.serializer(), mapOf("profile" to profile),
|
||||
WireJson.encodeToString(RecommendationHideInputBody.serializer(), body),
|
||||
)
|
||||
fun restore(profile: String, tmdb: Long, media: MediaType) = MutationCall(
|
||||
restore, RecommendationRestoreResult.serializer(), titleSegments(tmdb, media) + ("profile" to profile),
|
||||
)
|
||||
|
||||
private fun titleSegments(tmdb: Long, media: MediaType): Map<String, String> {
|
||||
require(tmdb > 0)
|
||||
return mapOf("tmdb" to tmdb.toString(), "type" to media.wire)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/** Shared wire encoding omits absent optional request fields. */
|
||||
internal val WireJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
encodeDefaults = false
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ActingCredit wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ActingCredit(
|
||||
@SerialName("character") val character: String,
|
||||
@SerialName("credit_id") val creditId: String,
|
||||
@SerialName("episode_count") val episodeCount: Long? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ActivityFeed wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ActivityFeed(
|
||||
@SerialName("items") val items: List<RequestProgress>?,
|
||||
@SerialName("requester") val requester: String,
|
||||
@SerialName("sources") val sources: List<EvidenceSource>?,
|
||||
@SerialName("truncated") val truncated: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Actor wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Actor(
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("requester") val requester: String,
|
||||
@SerialName("role") val role: Role,
|
||||
) {
|
||||
/** Wire values for role. */
|
||||
@Serializable
|
||||
enum class Role {
|
||||
@SerialName("admin") ADMIN,
|
||||
@SerialName("household") HOUSEHOLD,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Candidate wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Candidate(
|
||||
@SerialName("backdrop_path") val backdropPath: String? = null,
|
||||
@SerialName("contributions") val contributions: List<SeedContribution>?,
|
||||
@SerialName("genre_ids") val genreIds: List<Long>? = null,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("overview") val overview: String,
|
||||
@SerialName("poster_path") val posterPath: String,
|
||||
@SerialName("score") val score: Double,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("tvdb_id") val tvdbId: Long,
|
||||
@SerialName("year") val year: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** CastMember wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class CastMember(
|
||||
@SerialName("character") val character: String,
|
||||
@SerialName("credit_id") val creditId: String,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("order") val order: Long,
|
||||
@SerialName("performer_id") val performerId: Long,
|
||||
@SerialName("profile_url") val profileUrl: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Detail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Detail(
|
||||
@SerialName("genres") val genres: List<String>?,
|
||||
@SerialName("held_title") val heldTitle: HeldTitle? = null,
|
||||
@SerialName("metadata") val metadata: TitleMetadata,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("ratings") val ratings: List<Rating>? = null,
|
||||
@SerialName("runtime") val runtime: Long? = null,
|
||||
@SerialName("seasons") val seasons: List<Season>?,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("title") val title: Title,
|
||||
) {
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Discovery wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Discovery(
|
||||
@SerialName("genre_id") val genreId: Long,
|
||||
@SerialName("next") val next: String? = null,
|
||||
@SerialName("region") val region: String,
|
||||
@SerialName("service_id") val serviceId: Long,
|
||||
@SerialName("sort") val sort: Sort,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for sort. */
|
||||
@Serializable
|
||||
enum class Sort {
|
||||
@SerialName("trending") TRENDING,
|
||||
@SerialName("popular") POPULAR,
|
||||
@SerialName("new") NEW,
|
||||
@SerialName("top") TOP,
|
||||
@SerialName("upcoming") UPCOMING,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("empty") EMPTY,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("exhausted") EXHAUSTED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Enrichment wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Enrichment(
|
||||
@SerialName("genres") val genres: List<String>?,
|
||||
@SerialName("metadata") val metadata: TitleMetadata,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("ratings") val ratings: List<Rating>?,
|
||||
@SerialName("runtime") val runtime: Long? = null,
|
||||
@SerialName("seasons") val seasons: List<Season>?,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("title") val title: Title,
|
||||
) {
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Episode wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Episode(
|
||||
@SerialName("air_date") val airDate: String? = null,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("number") val number: Long,
|
||||
@SerialName("overview") val overview: String,
|
||||
@SerialName("recorded") val recorded: Recorded,
|
||||
) {
|
||||
/** Wire values for recorded. */
|
||||
@Serializable
|
||||
enum class Recorded {
|
||||
@SerialName("recorded") RECORDED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** EpisodeFileChip wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class EpisodeFileChip(
|
||||
@SerialName("quality") val quality: String? = null,
|
||||
@SerialName("release_name") val releaseName: String? = null,
|
||||
@SerialName("resolution") val resolution: String? = null,
|
||||
@SerialName("size_bytes") val sizeBytes: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** EpisodeRow wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class EpisodeRow(
|
||||
@SerialName("air_date") val airDate: String? = null,
|
||||
@SerialName("episode") val episode: Long,
|
||||
@SerialName("file") val file: EpisodeFileChip? = null,
|
||||
@SerialName("has_file") val hasFile: Boolean,
|
||||
@SerialName("key") val key: String,
|
||||
@SerialName("monitored") val monitored: Boolean,
|
||||
@SerialName("queue_state") val queueState: String? = null,
|
||||
@SerialName("runtime_minutes") val runtimeMinutes: Long? = null,
|
||||
@SerialName("season") val season: Long,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("watched") val watched: Map<String,Boolean>,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** EpisodeView wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class EpisodeView(
|
||||
@SerialName("owners") val owners: List<String>?,
|
||||
@SerialName("seasons") val seasons: List<SeasonGroup>?,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("users") val users: List<String>?,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
/** ErrorDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ErrorDetail(
|
||||
@SerialName("location") val location: String? = null,
|
||||
@SerialName("message") val message: String? = null,
|
||||
@SerialName("value") val `value`: JsonElement? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ErrorModel wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ErrorModel(
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("errors") val errors: List<ErrorDetail>? = null,
|
||||
@SerialName("instance") val instance: String? = null,
|
||||
@SerialName("status") val status: Long? = null,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("type") val type: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** EvidenceSource wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class EvidenceSource(
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** FileDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class FileDetail(
|
||||
@SerialName("audio") val audio: String? = null,
|
||||
@SerialName("container") val container: String? = null,
|
||||
@SerialName("quality") val quality: String? = null,
|
||||
@SerialName("release_name") val releaseName: String? = null,
|
||||
@SerialName("resolution") val resolution: String? = null,
|
||||
@SerialName("size_bytes") val sizeBytes: Long,
|
||||
@SerialName("video") val video: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** FilmographyPage wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class FilmographyPage(
|
||||
@SerialName("completeness") val completeness: Completeness,
|
||||
@SerialName("items") val items: List<FilmographyTitle>?,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("page") val page: Long,
|
||||
@SerialName("page_size") val pageSize: Long,
|
||||
@SerialName("performer_id") val performerId: Long,
|
||||
@SerialName("source") val source: Source,
|
||||
@SerialName("source_paging") val sourcePaging: SourcePaging,
|
||||
@SerialName("total_pages") val totalPages: Long? = null,
|
||||
@SerialName("total_results") val totalResults: Long? = null,
|
||||
) {
|
||||
/** Wire values for completeness. */
|
||||
@Serializable
|
||||
enum class Completeness {
|
||||
@SerialName("returned-non-adult-cast") RETURNED_NON_ADULT_CAST,
|
||||
}
|
||||
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("seerr") SEERR,
|
||||
}
|
||||
|
||||
/** Wire values for source_paging. */
|
||||
@Serializable
|
||||
enum class SourcePaging {
|
||||
@SerialName("unpaged") UNPAGED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** FilmographyTitle wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class FilmographyTitle(
|
||||
@SerialName("credits") val credits: List<ActingCredit>?,
|
||||
@SerialName("title") val title: Title,
|
||||
@SerialName("users") val users: List<String>?,
|
||||
@SerialName("visibility_state") val visibilityState: VisibilityState,
|
||||
) {
|
||||
/** Wire values for visibility_state. */
|
||||
@Serializable
|
||||
enum class VisibilityState {
|
||||
@SerialName("configured") CONFIGURED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Genre wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Genre(
|
||||
@SerialName("id") val id: Long,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** HealthDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class HealthDetail(
|
||||
@SerialName("failed") val failed: Boolean,
|
||||
@SerialName("has_file") val hasFile: Boolean,
|
||||
@SerialName("in_queue") val inQueue: Boolean,
|
||||
@SerialName("missing") val missing: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** HeldTitle wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class HeldTitle(
|
||||
@SerialName("detail") val detail: ItemDetail,
|
||||
@SerialName("episodes") val episodes: EpisodeView? = null,
|
||||
@SerialName("libraries") val libraries: List<String>?,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("versions") val versions: List<TitleVersion>?,
|
||||
@SerialName("watched") val watched: Map<String,Boolean>,
|
||||
) {
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("partial") PARTIAL,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ItemDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ItemDetail(
|
||||
@SerialName("backdrop_ref") val backdropRef: String? = null,
|
||||
@SerialName("file") val file: FileDetail? = null,
|
||||
@SerialName("health") val health: HealthDetail,
|
||||
@SerialName("is_anime") val isAnime: Boolean,
|
||||
@SerialName("media_type") val mediaType: String,
|
||||
@SerialName("owners") val owners: List<String>?,
|
||||
@SerialName("poster_ref") val posterRef: String? = null,
|
||||
@SerialName("runtime") val runtime: Long? = null,
|
||||
@SerialName("source") val source: SourceDetail,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("users") val users: List<String>?,
|
||||
@SerialName("year") val year: Long? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** LibraryAudience wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class LibraryAudience(
|
||||
@SerialName("is_anime") val isAnime: Boolean,
|
||||
@SerialName("libraries") val libraries: List<String>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("owner") val owner: String,
|
||||
@SerialName("users") val users: List<String>?,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** LibraryAudiencesBody wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class LibraryAudiencesBody(
|
||||
@SerialName("audiences") val audiences: List<LibraryAudience>?,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** LibraryObservation wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class LibraryObservation(
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("observed_at") val observedAt: String? = null,
|
||||
@SerialName("source") val source: String,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** OpenLink wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class OpenLink(
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("url") val url: String? = null,
|
||||
) {
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
@SerialName("unconfigured") UNCONFIGURED,
|
||||
@SerialName("not_permitted") NOT_PERMITTED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Operator wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Operator(
|
||||
@SerialName("capabilities") val capabilities: OperatorCapabilities,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("recommendation_profile") val recommendationProfile: String,
|
||||
@SerialName("requester") val requester: String,
|
||||
@SerialName("role") val role: Role,
|
||||
@SerialName("subject") val subject: String,
|
||||
@SerialName("watch_user") val watchUser: String,
|
||||
) {
|
||||
/** Wire values for role. */
|
||||
@Serializable
|
||||
enum class Role {
|
||||
@SerialName("admin") ADMIN,
|
||||
@SerialName("household") HOUSEHOLD,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** OperatorCapabilities wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class OperatorCapabilities(
|
||||
@SerialName("administer") val administer: Boolean,
|
||||
@SerialName("manage_library") val manageLibrary: Boolean,
|
||||
@SerialName("manage_memberships") val manageMemberships: Boolean,
|
||||
@SerialName("recommendation_profiles") val recommendationProfiles: List<String>?,
|
||||
@SerialName("request_media") val requestMedia: Boolean,
|
||||
@SerialName("requesters") val requesters: List<String>?,
|
||||
@SerialName("watch_users") val watchUsers: List<String>?,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Option wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Option(
|
||||
@SerialName("id") val id: Long,
|
||||
@SerialName("logo_url") val logoUrl: String? = null,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Options wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Options(
|
||||
@SerialName("genres") val genres: List<Option>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("region") val region: String,
|
||||
@SerialName("services") val services: List<Option>?,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Page wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Page(
|
||||
@SerialName("discovery") val discovery: Discovery? = null,
|
||||
@SerialName("items") val items: List<Title>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("page") val page: Long,
|
||||
@SerialName("query") val query: String,
|
||||
@SerialName("source") val source: Source,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("total_pages") val totalPages: Long,
|
||||
@SerialName("total_results") val totalResults: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("all") ALL,
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("search") SEARCH,
|
||||
@SerialName("general") GENERAL,
|
||||
@SerialName("related") RELATED,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("partial") PARTIAL,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** PerformerDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class PerformerDetail(
|
||||
@SerialName("also_known_as") val alsoKnownAs: List<String>? = null,
|
||||
@SerialName("biography") val biography: String? = null,
|
||||
@SerialName("birthday") val birthday: String? = null,
|
||||
@SerialName("deathday") val deathday: String? = null,
|
||||
@SerialName("known_for_department") val knownForDepartment: String? = null,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("name") val name: String? = null,
|
||||
@SerialName("performer_id") val performerId: Long,
|
||||
@SerialName("place_of_birth") val placeOfBirth: String? = null,
|
||||
@SerialName("profile_url") val profileUrl: String? = null,
|
||||
@SerialName("source") val source: Source,
|
||||
) {
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("seerr") SEERR,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Person wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Person(
|
||||
@SerialName("key") val key: String,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Provider wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Provider(
|
||||
@SerialName("id") val id: Long,
|
||||
@SerialName("logo_url") val logoUrl: String? = null,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Rating wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Rating(
|
||||
@SerialName("score") val score: Double? = null,
|
||||
@SerialName("source") val source: Source,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("status") val status: String? = null,
|
||||
@SerialName("url") val url: String? = null,
|
||||
@SerialName("votes") val votes: Long? = null,
|
||||
) {
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("rt_critics") RT_CRITICS,
|
||||
@SerialName("rt_audience") RT_AUDIENCE,
|
||||
@SerialName("imdb") IMDB,
|
||||
@SerialName("tmdb") TMDB,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("unrated") UNRATED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ReadProfile wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ReadProfile(
|
||||
@SerialName("key") val key: String,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ReadinessStage wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ReadinessStage(
|
||||
@SerialName("name") val name: Name,
|
||||
@SerialName("observed_at") val observedAt: String? = null,
|
||||
@SerialName("source") val source: String,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("summary") val summary: String,
|
||||
) {
|
||||
/** Wire values for name. */
|
||||
@Serializable
|
||||
enum class Name {
|
||||
@SerialName("request") REQUEST,
|
||||
@SerialName("acquisition") ACQUISITION,
|
||||
@SerialName("visibility") VISIBILITY,
|
||||
@SerialName("playback") PLAYBACK,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("done") DONE,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("in_progress") IN_PROGRESS,
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
@SerialName("stale") STALE,
|
||||
@SerialName("conflicting") CONFLICTING,
|
||||
@SerialName("failed") FAILED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationCoverage wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationCoverage(
|
||||
@SerialName("complete") val complete: Boolean,
|
||||
@SerialName("excluded") val excluded: Long,
|
||||
@SerialName("matched") val matched: Long,
|
||||
@SerialName("total") val total: Long,
|
||||
@SerialName("unresolved") val unresolved: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationDismissal wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationDismissal(
|
||||
@SerialName("hidden_at") val hiddenAt: String,
|
||||
@SerialName("hidden_by") val hiddenBy: String,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("poster_path") val posterPath: String,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("year") val year: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationDismissals wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationDismissals(
|
||||
@SerialName("dismissals") val dismissals: List<RecommendationDismissal>?,
|
||||
@SerialName("profile") val profile: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationFact wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationFact(
|
||||
@SerialName("genre_source") val genreSource: GenreSource,
|
||||
@SerialName("match") val match: Match,
|
||||
@SerialName("metadata") val metadata: TitleMetadata,
|
||||
) {
|
||||
/** Wire values for genre_source. */
|
||||
@Serializable
|
||||
enum class GenreSource {
|
||||
@SerialName("ranked") RANKED,
|
||||
@SerialName("metadata") METADATA,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
|
||||
/** Wire values for match. */
|
||||
@Serializable
|
||||
enum class Match {
|
||||
@SerialName("matched") MATCHED,
|
||||
@SerialName("excluded") EXCLUDED,
|
||||
@SerialName("unresolved") UNRESOLVED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationFeed wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationFeed(
|
||||
@SerialName("attempted_at") val attemptedAt: String? = null,
|
||||
@SerialName("expires_at") val expiresAt: String? = null,
|
||||
@SerialName("failure") val failure: Failure? = null,
|
||||
@SerialName("materialization") val materialization: Materialization,
|
||||
@SerialName("movies") val movies: List<Candidate>?,
|
||||
@SerialName("observed_at") val observedAt: String? = null,
|
||||
@SerialName("outcome") val outcome: Outcome,
|
||||
@SerialName("profile") val profile: ReadProfile,
|
||||
@SerialName("projection") val projection: RecommendationProjection,
|
||||
@SerialName("shows") val shows: List<Candidate>?,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for failure. */
|
||||
@Serializable
|
||||
enum class Failure {
|
||||
@SerialName("profile_resolution") PROFILE_RESOLUTION,
|
||||
@SerialName("memberships") MEMBERSHIPS,
|
||||
@SerialName("history") HISTORY,
|
||||
@SerialName("candidates") CANDIDATES,
|
||||
@SerialName("cycle") CYCLE,
|
||||
}
|
||||
|
||||
/** Wire values for materialization. */
|
||||
@Serializable
|
||||
enum class Materialization {
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
@SerialName("pending") PENDING,
|
||||
@SerialName("current") CURRENT,
|
||||
@SerialName("failed") FAILED,
|
||||
}
|
||||
|
||||
/** Wire values for outcome. */
|
||||
@Serializable
|
||||
enum class Outcome {
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
@SerialName("ranked") RANKED,
|
||||
@SerialName("no_seeds") NO_SEEDS,
|
||||
@SerialName("empty") EMPTY,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("current") CURRENT,
|
||||
@SerialName("stale") STALE,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationHideInputBody wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationHideInputBody(
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("poster_path") val posterPath: String? = null,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("year") val year: Long? = null,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationHideResult wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationHideResult(
|
||||
@SerialName("created") val created: Boolean,
|
||||
@SerialName("dismissal") val dismissal: RecommendationDismissal,
|
||||
@SerialName("materialization") val materialization: Materialization,
|
||||
@SerialName("profile") val profile: String,
|
||||
) {
|
||||
/** Wire values for materialization. */
|
||||
@Serializable
|
||||
enum class Materialization {
|
||||
@SerialName("current") CURRENT,
|
||||
@SerialName("failed") FAILED,
|
||||
@SerialName("unchanged") UNCHANGED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationProfiles wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationProfiles(
|
||||
@SerialName("default_profile") val defaultProfile: String,
|
||||
@SerialName("profiles") val profiles: List<ReadProfile>?,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationProjection wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationProjection(
|
||||
@SerialName("coverage") val coverage: RecommendationCoverage,
|
||||
@SerialName("facts") val facts: List<RecommendationFact>?,
|
||||
@SerialName("genre_id") val genreId: Long,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("service_id") val serviceId: Long,
|
||||
@SerialName("unresolved") val unresolved: List<Candidate>?,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("all") ALL,
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RecommendationRestoreResult wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RecommendationRestoreResult(
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("profile") val profile: String,
|
||||
@SerialName("restored") val restored: Boolean,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestIntentBody wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestIntentBody(
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("recommendation_profile") val recommendationProfile: String? = null,
|
||||
@SerialName("requester") val requester: String? = null,
|
||||
@SerialName("seasons") val seasons: List<Long>? = null,
|
||||
@SerialName("source") val source: Source? = null,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("title") TITLE,
|
||||
@SerialName("recommendation") RECOMMENDATION,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestProgress wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestProgress(
|
||||
@SerialName("actor") val actor: Actor? = null,
|
||||
@SerialName("awaiting_approval") val awaitingApproval: Boolean? = null,
|
||||
@SerialName("blocking_stage") val blockingStage: BlockingStage? = null,
|
||||
@SerialName("filed_by_you") val filedByYou: Boolean,
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("on_behalf") val onBehalf: Boolean,
|
||||
@SerialName("requested_at") val requestedAt: String? = null,
|
||||
@SerialName("requester") val requester: Person,
|
||||
@SerialName("seasons") val seasons: List<Long>?,
|
||||
@SerialName("stages") val stages: List<ReadinessStage>?,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for blocking_stage. */
|
||||
@Serializable
|
||||
enum class BlockingStage {
|
||||
@SerialName("request") REQUEST,
|
||||
@SerialName("acquisition") ACQUISITION,
|
||||
@SerialName("visibility") VISIBILITY,
|
||||
@SerialName("playback") PLAYBACK,
|
||||
}
|
||||
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestReceipt wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestReceipt(
|
||||
@SerialName("actor") val actor: RequestReceiptActor,
|
||||
@SerialName("attempts") val attempts: Long,
|
||||
@SerialName("coverage") val coverage: List<RequestReceiptCoverage>?,
|
||||
@SerialName("created_at") val createdAt: String,
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("id") val id: Long,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("membership") val membership: RequestReceiptMembership,
|
||||
@SerialName("on_behalf") val onBehalf: Boolean,
|
||||
@SerialName("recommendation_profile") val recommendationProfile: String? = null,
|
||||
@SerialName("requester") val requester: String,
|
||||
@SerialName("requester_username") val requesterUsername: String,
|
||||
@SerialName("seasons") val seasons: List<Long>?,
|
||||
@SerialName("seerr") val seerr: RequestReceiptSeerr,
|
||||
@SerialName("source") val source: Source,
|
||||
@SerialName("state") val state: State,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("updated_at") val updatedAt: String,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("title") TITLE,
|
||||
@SerialName("recommendation") RECOMMENDATION,
|
||||
}
|
||||
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("settled") SETTLED,
|
||||
@SerialName("pending") PENDING,
|
||||
@SerialName("retired") RETIRED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestReceiptActor wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestReceiptActor(
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("requester") val requester: String,
|
||||
@SerialName("role") val role: Role,
|
||||
) {
|
||||
/** Wire values for role. */
|
||||
@Serializable
|
||||
enum class Role {
|
||||
@SerialName("admin") ADMIN,
|
||||
@SerialName("household") HOUSEHOLD,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestReceiptCoverage wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestReceiptCoverage(
|
||||
@SerialName("outcome") val outcome: Outcome,
|
||||
@SerialName("request_id") val requestId: Long? = null,
|
||||
@SerialName("season") val season: Long,
|
||||
) {
|
||||
/** Wire values for outcome. */
|
||||
@Serializable
|
||||
enum class Outcome {
|
||||
@SerialName("requested") REQUESTED,
|
||||
@SerialName("already_requested") ALREADY_REQUESTED,
|
||||
@SerialName("already_held") ALREADY_HELD,
|
||||
@SerialName("unresolved") UNRESOLVED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestReceiptMembership wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestReceiptMembership(
|
||||
@SerialName("bucket") val bucket: String? = null,
|
||||
@SerialName("dispatch_log_id") val dispatchLogId: Long? = null,
|
||||
@SerialName("status") val status: Status,
|
||||
) {
|
||||
/** Wire values for status. */
|
||||
@Serializable
|
||||
enum class Status {
|
||||
@SerialName("added") ADDED,
|
||||
@SerialName("already_owned") ALREADY_OWNED,
|
||||
@SerialName("pending_dispatch") PENDING_DISPATCH,
|
||||
@SerialName("conflict") CONFLICT,
|
||||
@SerialName("failed") FAILED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** RequestReceiptSeerr wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class RequestReceiptSeerr(
|
||||
@SerialName("outcome") val outcome: Outcome,
|
||||
@SerialName("request_id") val requestId: Long? = null,
|
||||
) {
|
||||
/** Wire values for outcome. */
|
||||
@Serializable
|
||||
enum class Outcome {
|
||||
@SerialName("requested") REQUESTED,
|
||||
@SerialName("already_requested") ALREADY_REQUESTED,
|
||||
@SerialName("not_needed") NOT_NEEDED,
|
||||
@SerialName("not_attempted") NOT_ATTEMPTED,
|
||||
@SerialName("unresolved") UNRESOLVED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Season wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Season(
|
||||
@SerialName("air_date") val airDate: String? = null,
|
||||
@SerialName("coverage") val coverage: Coverage,
|
||||
@SerialName("episode_count") val episodeCount: Long,
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("number") val number: Long,
|
||||
@SerialName("overview") val overview: String,
|
||||
@SerialName("recorded_episodes") val recordedEpisodes: Long,
|
||||
@SerialName("request_state") val requestState: RequestState,
|
||||
@SerialName("seerr_state") val seerrState: SeerrState,
|
||||
) {
|
||||
/** Wire values for coverage. */
|
||||
@Serializable
|
||||
enum class Coverage {
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("complete") COMPLETE,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
|
||||
/** Wire values for request_state. */
|
||||
@Serializable
|
||||
enum class RequestState {
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("requested") REQUESTED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
|
||||
/** Wire values for seerr_state. */
|
||||
@Serializable
|
||||
enum class SeerrState {
|
||||
@SerialName("untracked") UNTRACKED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
@SerialName("pending") PENDING,
|
||||
@SerialName("processing") PROCESSING,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("available") AVAILABLE,
|
||||
@SerialName("blocklisted") BLOCKLISTED,
|
||||
@SerialName("deleted") DELETED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeasonCoverage wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeasonCoverage(
|
||||
@SerialName("coverage") val coverage: Coverage,
|
||||
@SerialName("season") val season: Long,
|
||||
) {
|
||||
/** Wire values for coverage. */
|
||||
@Serializable
|
||||
enum class Coverage {
|
||||
@SerialName("complete") COMPLETE,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeasonDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeasonDetail(
|
||||
@SerialName("episodes") val episodes: List<Episode>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("observations") val observations: List<LibraryObservation>?,
|
||||
@SerialName("season") val season: Season,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeasonGroup wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeasonGroup(
|
||||
@SerialName("episodes") val episodes: List<EpisodeRow>?,
|
||||
@SerialName("season") val season: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeedContribution wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeedContribution(
|
||||
@SerialName("added") val added: Boolean,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("watched") val watched: Boolean,
|
||||
) {
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeerrObservation wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeerrObservation(
|
||||
@SerialName("detail") val detail: String? = null,
|
||||
@SerialName("requests") val requests: List<SeerrRequest>?,
|
||||
@SerialName("state") val state: State,
|
||||
) {
|
||||
/** Wire values for state. */
|
||||
@Serializable
|
||||
enum class State {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SeerrRequest wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SeerrRequest(
|
||||
@SerialName("id") val id: Long,
|
||||
@SerialName("status") val status: Status,
|
||||
) {
|
||||
/** Wire values for status. */
|
||||
@Serializable
|
||||
enum class Status {
|
||||
@SerialName("pending") PENDING,
|
||||
@SerialName("approved") APPROVED,
|
||||
@SerialName("declined") DECLINED,
|
||||
@SerialName("failed") FAILED,
|
||||
@SerialName("completed") COMPLETED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** SourceDetail wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class SourceDetail(
|
||||
@SerialName("arr") val arr: String,
|
||||
@SerialName("imported") val imported: String? = null,
|
||||
@SerialName("indexer") val indexer: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Title wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class Title(
|
||||
@SerialName("backdrop_url") val backdropUrl: String? = null,
|
||||
@SerialName("genre_ids") val genreIds: List<Long>? = null,
|
||||
@SerialName("held") val held: Held,
|
||||
@SerialName("libraries") val libraries: List<String>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("overview") val overview: String,
|
||||
@SerialName("poster_url") val posterUrl: String? = null,
|
||||
@SerialName("release_date") val releaseDate: String? = null,
|
||||
@SerialName("request_state") val requestState: RequestState,
|
||||
@SerialName("seerr_state") val seerrState: SeerrState,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for held. */
|
||||
@Serializable
|
||||
enum class Held {
|
||||
@SerialName("held") HELD,
|
||||
@SerialName("unheld") UNHELD,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for request_state. */
|
||||
@Serializable
|
||||
enum class RequestState {
|
||||
@SerialName("none") NONE,
|
||||
@SerialName("requested") REQUESTED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
}
|
||||
|
||||
/** Wire values for seerr_state. */
|
||||
@Serializable
|
||||
enum class SeerrState {
|
||||
@SerialName("untracked") UNTRACKED,
|
||||
@SerialName("unknown") UNKNOWN,
|
||||
@SerialName("pending") PENDING,
|
||||
@SerialName("processing") PROCESSING,
|
||||
@SerialName("partial") PARTIAL,
|
||||
@SerialName("available") AVAILABLE,
|
||||
@SerialName("blocklisted") BLOCKLISTED,
|
||||
@SerialName("deleted") DELETED,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** TitleCast wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class TitleCast(
|
||||
@SerialName("credit_context") val creditContext: CreditContext,
|
||||
@SerialName("items") val items: List<CastMember>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("metadata_state") val metadataState: MetadataState,
|
||||
@SerialName("paging") val paging: Paging,
|
||||
@SerialName("source") val source: Source,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for credit_context. */
|
||||
@Serializable
|
||||
enum class CreditContext {
|
||||
@SerialName("movie-character") MOVIE_CHARACTER,
|
||||
@SerialName("tv-leading-character") TV_LEADING_CHARACTER,
|
||||
}
|
||||
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for metadata_state. */
|
||||
@Serializable
|
||||
enum class MetadataState {
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for paging. */
|
||||
@Serializable
|
||||
enum class Paging {
|
||||
@SerialName("unpaged") UNPAGED,
|
||||
}
|
||||
|
||||
/** Wire values for source. */
|
||||
@Serializable
|
||||
enum class Source {
|
||||
@SerialName("seerr") SEERR,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** TitleMetadata wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class TitleMetadata(
|
||||
@SerialName("checked_at") val checkedAt: String? = null,
|
||||
@SerialName("genre_state") val genreState: GenreState,
|
||||
@SerialName("genres") val genres: List<Genre>?,
|
||||
@SerialName("genres_observed_at") val genresObservedAt: String? = null,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("provider_state") val providerState: ProviderState,
|
||||
@SerialName("providers") val providers: List<Provider>?,
|
||||
@SerialName("providers_observed_at") val providersObservedAt: String? = null,
|
||||
@SerialName("region") val region: Region,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
) {
|
||||
/** Wire values for genre_state. */
|
||||
@Serializable
|
||||
enum class GenreState {
|
||||
@SerialName("unobserved") UNOBSERVED,
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
|
||||
/** Wire values for provider_state. */
|
||||
@Serializable
|
||||
enum class ProviderState {
|
||||
@SerialName("unobserved") UNOBSERVED,
|
||||
@SerialName("observed") OBSERVED,
|
||||
@SerialName("missing") MISSING,
|
||||
@SerialName("unavailable") UNAVAILABLE,
|
||||
}
|
||||
|
||||
/** Wire values for region. */
|
||||
@Serializable
|
||||
enum class Region {
|
||||
@SerialName("NL") NL,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** TitleRequestsBody wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class TitleRequestsBody(
|
||||
@SerialName("requests") val requests: List<RequestReceipt>?,
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** TitleVersion wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class TitleVersion(
|
||||
@SerialName("file") val file: FileDetail? = null,
|
||||
@SerialName("role") val role: Role,
|
||||
@SerialName("seasons") val seasons: List<SeasonGroup>? = null,
|
||||
@SerialName("source") val source: String,
|
||||
) {
|
||||
/** Wire values for role. */
|
||||
@Serializable
|
||||
enum class Role {
|
||||
@SerialName("primary") PRIMARY,
|
||||
@SerialName("mirror") MIRROR,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
package org.justinvisser.wolfskin.conductarr.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** ViewerReadiness wire contract from the pinned Conductarr schema. */
|
||||
@Serializable
|
||||
data class ViewerReadiness(
|
||||
@SerialName("blocking_stage") val blockingStage: BlockingStage? = null,
|
||||
@SerialName("coverage") val coverage: List<SeasonCoverage>?,
|
||||
@SerialName("media_type") val mediaType: MediaType,
|
||||
@SerialName("open_link") val openLink: OpenLink,
|
||||
@SerialName("seerr") val seerr: SeerrObservation,
|
||||
@SerialName("sources") val sources: List<EvidenceSource>?,
|
||||
@SerialName("stages") val stages: List<ReadinessStage>?,
|
||||
@SerialName("title") val title: String? = null,
|
||||
@SerialName("tmdb_id") val tmdbId: Long,
|
||||
@SerialName("viewer") val viewer: Person,
|
||||
) {
|
||||
/** Wire values for blocking_stage. */
|
||||
@Serializable
|
||||
enum class BlockingStage {
|
||||
@SerialName("request") REQUEST,
|
||||
@SerialName("acquisition") ACQUISITION,
|
||||
@SerialName("visibility") VISIBILITY,
|
||||
@SerialName("playback") PLAYBACK,
|
||||
}
|
||||
|
||||
/** Wire values for media_type. */
|
||||
@Serializable
|
||||
enum class MediaType {
|
||||
@SerialName("movie") MOVIE,
|
||||
@SerialName("tv") TV,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue