mirror of
https://github.com/jellyfin/jellyfin-sdk-kotlin.git
synced 2025-02-22 01:01:05 +00:00
commit
3c3fb57a59
@ -12,7 +12,11 @@ dependencies {
|
||||
implementation(Dependencies.Ktor.okhttp)
|
||||
implementation(Dependencies.Ktor.serialization)
|
||||
|
||||
// Testing
|
||||
// Logging
|
||||
implementation(Dependencies.Slf4j.api)
|
||||
testImplementation(Dependencies.Slf4j.simple)
|
||||
|
||||
// Unit testing
|
||||
testImplementation(Dependencies.Kotlin.Test.junit)
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,11 @@ import io.ktor.client.call.*
|
||||
import io.ktor.client.features.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.features.websocket.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jellyfin.apiclient.model.ClientInfo
|
||||
import org.jellyfin.apiclient.model.DeviceInfo
|
||||
import kotlin.collections.set
|
||||
@ -18,14 +20,23 @@ open class KtorClient(
|
||||
var clientInfo: ClientInfo,
|
||||
var deviceInfo: DeviceInfo
|
||||
) : ApiClient {
|
||||
val json = Json {
|
||||
isLenient = false
|
||||
ignoreUnknownKeys = true
|
||||
allowSpecialFloatingPointValues = true
|
||||
useArrayPolymorphism = false
|
||||
}
|
||||
|
||||
val client = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer()
|
||||
serializer = KotlinxSerializer(json)
|
||||
}
|
||||
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = 10000
|
||||
connectTimeoutMillis = 10000
|
||||
}
|
||||
|
||||
install(WebSockets)
|
||||
}
|
||||
|
||||
override fun createPath(path: String, pathParameters: Map<String, Any?>) = path
|
||||
|
@ -9,6 +9,6 @@ import org.jellyfin.apiclient.model.api.AuthenticateUserByName
|
||||
suspend inline fun UserApi.authenticateUserByName(username: String, password: String) = authenticateUserByName(
|
||||
data = AuthenticateUserByName(
|
||||
username = username,
|
||||
password = password
|
||||
pw = password
|
||||
)
|
||||
)
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.jellyfin.apiclient.api.sockets
|
||||
|
||||
import org.jellyfin.apiclient.model.socket.IncomingSocketMessage
|
||||
|
||||
/**
|
||||
* Subscription to the [WebSocketApi] that can be cancelled.
|
||||
*/
|
||||
class SocketSubscription(
|
||||
private val webSocketApi: WebSocketApi,
|
||||
internal val callback: (IncomingSocketMessage) -> Unit
|
||||
) {
|
||||
/**
|
||||
* Cancel the subscription and stop listening for messages.
|
||||
*/
|
||||
suspend fun cancel() = webSocketApi.cancelSubscription(this)
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
package org.jellyfin.apiclient.api.sockets
|
||||
|
||||
import io.ktor.client.features.websocket.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.*
|
||||
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlinx.serialization.serializer
|
||||
import org.jellyfin.apiclient.api.client.KtorClient
|
||||
import org.jellyfin.apiclient.model.socket.*
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Provides realtime communication with the Jellyfin server.
|
||||
* Automatically connects when subscribed to or when messages are sent and disconnects when no subscribers left.
|
||||
*
|
||||
* When the connections drops it will reconnect automatically.
|
||||
* Will not react to access token or server url changes, call .reconnect() to act on these changes.
|
||||
*
|
||||
* The user should verify the access token is correct as the server does not respond to bad authorization.
|
||||
*/
|
||||
class WebSocketApi(
|
||||
private val api: KtorClient
|
||||
) {
|
||||
companion object {
|
||||
// Mapping for types to message
|
||||
val incomingMessageSerializers = mapOf(
|
||||
"ForceKeepAlive" to serializer<ForceKeepAliveMessage>(),
|
||||
"GeneralCommand" to serializer<GeneralCommandMessage>(),
|
||||
"UserDataChanged" to serializer<UserDataChangedMessage>(),
|
||||
"Sessions" to serializer<SessionsMessage>(),
|
||||
"Play" to serializer<PlayMessage>(),
|
||||
"SyncPlayCommand" to serializer<SyncPlayCommandMessage>(),
|
||||
"SyncPlayGroupUpdate" to serializer<SyncPlayGroupUpdateMessage>(),
|
||||
"PlayState" to serializer<PlayStateMessage>(),
|
||||
"RestartRequired" to serializer<RestartRequiredMessage>(),
|
||||
"ServerShuttingDown" to serializer<ServerShuttingDownMessage>(),
|
||||
"ServerRestarting" to serializer<ServerRestartingMessage>(),
|
||||
"LibraryChanged" to serializer<LibraryChangedMessage>(),
|
||||
"UserDeleted" to serializer<UserDeletedMessage>(),
|
||||
"UserUpdated" to serializer<UserUpdatedMessage>(),
|
||||
"SeriesTimerCreated" to serializer<SeriesTimerCreatedMessage>(),
|
||||
"TimerCreated" to serializer<TimerCreatedMessage>(),
|
||||
"SeriesTimerCancelled" to serializer<SeriesTimerCancelledMessage>(),
|
||||
"TimerCancelled" to serializer<TimerCancelledMessage>(),
|
||||
"RefreshProgress" to serializer<RefreshProgressMessage>(),
|
||||
"ScheduledTaskEnded" to serializer<ScheduledTaskEndedMessage>(),
|
||||
"PackageInstallationCancelled" to serializer<PackageInstallationCancelledMessage>(),
|
||||
"PackageInstallationFailed" to serializer<PackageInstallationFailedMessage>(),
|
||||
"PackageInstallationCompleted" to serializer<PackageInstallationCompletedMessage>(),
|
||||
"PackageInstalling" to serializer<PackageInstallingMessage>(),
|
||||
"PackageUninstalled" to serializer<PackageUninstalledMessage>(),
|
||||
"ActivityLogEntry" to serializer<ActivityLogEntryMessage>(),
|
||||
"ScheduledTasksInfo" to serializer<ScheduledTasksInfoMessage>(),
|
||||
|
||||
// Shared type, only implemented as outgoing message
|
||||
"KeepAlive" to serializer<KeepAliveMessage>(),
|
||||
)
|
||||
|
||||
private const val JSON_PROP_DATA = "Data"
|
||||
private const val JSON_PROP_MESSAGE_ID = "MessageId"
|
||||
private const val JSON_PROP_MESSAGE_TYPE = "MessageType"
|
||||
private const val RECONNECT_DELAY = 3 * 1000L // milliseconds
|
||||
}
|
||||
|
||||
private val logger = LoggerFactory.getLogger("WebSocketApi")
|
||||
|
||||
private val json = Json {
|
||||
// Based on DefaultJson
|
||||
isLenient = false
|
||||
ignoreUnknownKeys = true
|
||||
allowSpecialFloatingPointValues = true
|
||||
useArrayPolymorphism = false
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
private var socketJob: Job? = null
|
||||
private val subscriptions = mutableListOf<SocketSubscription>()
|
||||
|
||||
private val outgoingMessageChannel by lazy {
|
||||
Channel<String>(BUFFERED)
|
||||
}
|
||||
|
||||
private suspend fun ReceiveChannel<Frame>.read() = consumeAsFlow()
|
||||
.onEach {
|
||||
val message = it.readSocketMessage() ?: return@onEach
|
||||
|
||||
// Send message to subscriptions
|
||||
subscriptions.forEach { subscription -> subscription.callback(message) }
|
||||
}
|
||||
.catch { logger.error(it) }
|
||||
.onCompletion {
|
||||
// Reconnect
|
||||
logger.debug("Socket receiver completed, found %s subscriptions", subscriptions.size)
|
||||
delay(RECONNECT_DELAY)
|
||||
if (subscriptions.isNotEmpty()) reconnect()
|
||||
}
|
||||
.collect()
|
||||
|
||||
private suspend fun SendChannel<Frame>.write() = outgoingMessageChannel
|
||||
.receiveAsFlow()
|
||||
.onEach { text ->
|
||||
logger.info("Sending message {}", text)
|
||||
send(Frame.Text(text))
|
||||
}
|
||||
.catch { logger.error(it) }
|
||||
.collect()
|
||||
|
||||
private suspend fun subscriptionsChanged() {
|
||||
logger.debug("Subscriptions changed to %s", subscriptions.size)
|
||||
|
||||
if (socketJob != null && subscriptions.isEmpty()) {
|
||||
logger.info("Dropping connection")
|
||||
socketJob?.cancel()
|
||||
} else if (socketJob == null && subscriptions.isNotEmpty()) {
|
||||
logger.info("Creating connection")
|
||||
reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to (re)connect the WebSocket. Does not close current listeners.
|
||||
*/
|
||||
suspend fun reconnect() {
|
||||
// Get access token and check if it's not null
|
||||
val accessToken = checkNotNull(api.accessToken)
|
||||
|
||||
logger.debug("Reconnect requested")
|
||||
|
||||
// Close existing socket
|
||||
socketJob?.cancel()
|
||||
|
||||
// Open socket
|
||||
socketJob = GlobalScope.launch {
|
||||
// Create web socket connection
|
||||
api.client.ws({
|
||||
url {
|
||||
takeFrom(api.createUrl(
|
||||
pathTemplate = "/socket",
|
||||
queryParameters = mapOf(
|
||||
"api_key" to accessToken,
|
||||
"deviceId" to api.deviceInfo.id
|
||||
)
|
||||
))
|
||||
|
||||
protocol = when (protocol) {
|
||||
URLProtocol.HTTP -> URLProtocol.WS
|
||||
URLProtocol.HTTPS -> URLProtocol.WSS
|
||||
|
||||
// Default to WS
|
||||
else -> URLProtocol.WS
|
||||
}
|
||||
}
|
||||
}) {
|
||||
// Bind to messaging channels
|
||||
joinAll(
|
||||
launch { incoming.read() },
|
||||
launch { outgoing.write() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Frame.readSocketMessage(): IncomingSocketMessage? {
|
||||
require(this is Frame.Text)
|
||||
|
||||
// Read text from frame
|
||||
val text = readText()
|
||||
logger.info("Received message {}", text)
|
||||
|
||||
// Read JSON object from text
|
||||
val json = json.parseToJsonElement(text)
|
||||
require(json is JsonObject)
|
||||
|
||||
// Read id
|
||||
val messageId = json[JSON_PROP_MESSAGE_ID]?.jsonPrimitive?.content
|
||||
require(messageId is String)
|
||||
|
||||
// Read type
|
||||
val type = json[JSON_PROP_MESSAGE_TYPE]?.jsonPrimitive?.content
|
||||
require(type is String)
|
||||
|
||||
// Get serializer for type
|
||||
val dataSerializer = incomingMessageSerializers[type]
|
||||
if (dataSerializer == null) {
|
||||
logger.warn("Message type {} appears to be missing in the incomingMessageSerializers map.", type)
|
||||
return null
|
||||
}
|
||||
|
||||
// Modify JSON to flatten the Data object
|
||||
val modifiedJson = buildJsonObject {
|
||||
put(JSON_PROP_MESSAGE_ID, messageId)
|
||||
|
||||
// Flatten data object or keep it when it's not an object
|
||||
val data = json[JSON_PROP_DATA]
|
||||
if (data is JsonObject) data.entries.forEach { (key, value) -> put(key, value) }
|
||||
else put(JSON_PROP_DATA, data!!)
|
||||
}
|
||||
|
||||
return api.json.decodeFromJsonElement(dataSerializer, modifiedJson) as? IncomingSocketMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a message to the server.
|
||||
*/
|
||||
suspend inline fun <reified T : OutgoingSocketMessage> publish(message: T) = publish(message, serializer())
|
||||
|
||||
/**
|
||||
* Publish a message to the server.
|
||||
*/
|
||||
suspend fun <T : OutgoingSocketMessage> publish(message: T, serializer: KSerializer<T>) {
|
||||
val jsonObject = json.encodeToJsonElement(serializer, message).jsonObject
|
||||
val messageType = serializer.descriptor.serialName
|
||||
|
||||
val text = json.encodeToString(buildJsonObject {
|
||||
put(JSON_PROP_MESSAGE_TYPE, messageType)
|
||||
|
||||
val data = jsonObject[JSON_PROP_DATA]
|
||||
if (data != null) put(JSON_PROP_DATA, data)
|
||||
else putJsonObject(JSON_PROP_DATA) {
|
||||
jsonObject.entries
|
||||
.filterNot { (key, _) -> key == JSON_PROP_MESSAGE_TYPE }
|
||||
.forEach { (key, value) -> put(key, value) }
|
||||
}
|
||||
})
|
||||
|
||||
outgoingMessageChannel.send(text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to messages. Calls the [block] for each incoming message until
|
||||
* [SocketSubscription.cancel] is invoked.
|
||||
*/
|
||||
suspend fun subscribe(block: (IncomingSocketMessage) -> Unit): SocketSubscription {
|
||||
val subscription = SocketSubscription(this, block)
|
||||
subscriptions += subscription
|
||||
subscriptionsChanged()
|
||||
|
||||
return subscription
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Flow] based version of a subscription.
|
||||
*/
|
||||
suspend fun subscribe() = callbackFlow {
|
||||
// Create subscription and send messages to flow
|
||||
val subscription = subscribe {
|
||||
sendBlocking(it)
|
||||
}
|
||||
|
||||
// Cancel subscription when flow closes
|
||||
awaitClose {
|
||||
runBlocking {
|
||||
subscription.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a subscription by removing it from the API.
|
||||
* Automatically closes the WebSocket if no subscriptions are left.
|
||||
*/
|
||||
suspend fun cancelSubscription(subscription: SocketSubscription) {
|
||||
subscriptions -= subscription
|
||||
subscriptionsChanged()
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeParseException
|
||||
|
||||
/**
|
||||
* Serializer to read zoned date times as local date time and writing it back
|
||||
@ -16,8 +17,13 @@ import java.time.ZonedDateTime
|
||||
class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime =
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime = try {
|
||||
ZonedDateTime.parse(decoder.decodeString()).toLocalDateTime()
|
||||
} catch (err: DateTimeParseException) {
|
||||
// Server will sometimes return 0001-01-01T00:00:00
|
||||
// but java.time can't parse that
|
||||
LocalDateTime.MIN
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) =
|
||||
encoder.encodeString(value.atZone(ZoneId.systemDefault()).toString())
|
||||
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.ActivityLogEntry
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ActivityLogEntryMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val entries: List<ActivityLogEntry>,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,11 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("ActivityLogEntryStart")
|
||||
data class ActivityLogEntryStartMessage(
|
||||
@SerialName("Data")
|
||||
val period: PeriodicListenerPeriod = PeriodicListenerPeriod(),
|
||||
) : OutgoingSocketMessage
|
@ -0,0 +1,8 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("ActivityLogEntryStop")
|
||||
class ActivityLogEntryStopMessage : OutgoingSocketMessage
|
@ -0,0 +1,18 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ForceKeepAliveMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val value: Int,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,24 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class GeneralCommandMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Name")
|
||||
val command: GeneralCommandType,
|
||||
|
||||
@SerialName("ControllingUserId")
|
||||
val userId: UUID,
|
||||
|
||||
@SerialName("Arguments")
|
||||
val arguments: Map<String, String> = emptyMap(),
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,118 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class GeneralCommandType {
|
||||
@SerialName("MoveUp")
|
||||
MoveUp,
|
||||
|
||||
@SerialName("MoveDown")
|
||||
MoveDown,
|
||||
|
||||
@SerialName("MoveLeft")
|
||||
MoveLeft,
|
||||
|
||||
@SerialName("MoveRight")
|
||||
MoveRight,
|
||||
|
||||
@SerialName("PageUp")
|
||||
PageUp,
|
||||
|
||||
@SerialName("PageDown")
|
||||
PageDown,
|
||||
|
||||
@SerialName("PreviousLetter")
|
||||
PreviousLetter,
|
||||
|
||||
@SerialName("NextLetter")
|
||||
NextLetter,
|
||||
|
||||
@SerialName("ToggleOsd")
|
||||
ToggleOsd,
|
||||
|
||||
@SerialName("ToggleContextMenu")
|
||||
ToggleContextMenu,
|
||||
|
||||
@SerialName("Select")
|
||||
Select,
|
||||
|
||||
@SerialName("Back")
|
||||
Back,
|
||||
|
||||
@SerialName("TakeScreenshot")
|
||||
TakeScreenshot,
|
||||
|
||||
@SerialName("SendKey")
|
||||
SendKey,
|
||||
|
||||
@SerialName("SendString")
|
||||
SendString,
|
||||
|
||||
@SerialName("GoHome")
|
||||
GoHome,
|
||||
|
||||
@SerialName("GoToSettings")
|
||||
GoToSettings,
|
||||
|
||||
@SerialName("VolumeUp")
|
||||
VolumeUp,
|
||||
|
||||
@SerialName("VolumeDown")
|
||||
VolumeDown,
|
||||
|
||||
@SerialName("Mute")
|
||||
Mute,
|
||||
|
||||
@SerialName("Unmute")
|
||||
Unmute,
|
||||
|
||||
@SerialName("ToggleMute")
|
||||
ToggleMute,
|
||||
|
||||
@SerialName("SetVolume")
|
||||
SetVolume,
|
||||
|
||||
@SerialName("SetAudioStreamIndex")
|
||||
SetAudioStreamIndex,
|
||||
|
||||
@SerialName("SetSubtitleStreamIndex")
|
||||
SetSubtitleStreamIndex,
|
||||
|
||||
@SerialName("ToggleFullscreen")
|
||||
ToggleFullscreen,
|
||||
|
||||
@SerialName("DisplayContent")
|
||||
DisplayContent,
|
||||
|
||||
@SerialName("GoToSearch")
|
||||
GoToSearch,
|
||||
|
||||
@SerialName("DisplayMessage")
|
||||
DisplayMessage,
|
||||
|
||||
@SerialName("SetRepeatMode")
|
||||
SetRepeatMode,
|
||||
|
||||
@SerialName("ChannelUp")
|
||||
ChannelUp,
|
||||
|
||||
@SerialName("ChannelDown")
|
||||
ChannelDown,
|
||||
|
||||
@SerialName("SetMaxStreamingBitrate")
|
||||
SetMaxStreamingBitrate,
|
||||
|
||||
@SerialName("Guide")
|
||||
Guide,
|
||||
|
||||
@SerialName("ToggleStats")
|
||||
ToggleStats,
|
||||
|
||||
@SerialName("PlayMediaSource")
|
||||
PlayMediaSource,
|
||||
|
||||
@SerialName("PlayTrailers")
|
||||
PlayTrailers,
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
interface IncomingSocketMessage : SocketMessage {
|
||||
/**
|
||||
* The id of the received message.
|
||||
*
|
||||
* Implementation note: Copy the @SerialName notation to the implementation side
|
||||
*/
|
||||
@SerialName("MessageId")
|
||||
val messageId: UUID
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("KeepAlive")
|
||||
class KeepAliveMessage : OutgoingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.LibraryUpdateInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class LibraryChangedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: LibraryUpdateInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,4 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
interface OutgoingSocketMessage : SocketMessage
|
||||
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.InstallationInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PackageInstallationCancelledMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: InstallationInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.InstallationInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PackageInstallationCompletedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: InstallationInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.InstallationInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PackageInstallationFailedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: InstallationInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.InstallationInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PackageInstallingMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: InstallationInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.IPlugin
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PackageUninstalledMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val plugin: IPlugin,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,41 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@Serializable(with = PeriodicListenerPeriod.Serializer::class)
|
||||
data class PeriodicListenerPeriod(
|
||||
val initialDelay: Long = 0,
|
||||
val interval: Long = 1000,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "$initialDelay,$interval"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromString(str: String): PeriodicListenerPeriod? {
|
||||
val values = str.split(',')
|
||||
val dueTimeMs = values.getOrNull(0)?.toLongOrNull() ?: return null
|
||||
val periodMs = values.getOrNull(1)?.toLongOrNull() ?: return null
|
||||
|
||||
return PeriodicListenerPeriod(
|
||||
initialDelay = dueTimeMs,
|
||||
interval = periodMs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Serializer : KSerializer<PeriodicListenerPeriod> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("PeriodicListenerPeriod", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: PeriodicListenerPeriod) =
|
||||
encoder.encodeString(value.toString())
|
||||
|
||||
override fun deserialize(decoder: Decoder): PeriodicListenerPeriod =
|
||||
fromString(decoder.decodeString())!!
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.PlayRequest
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PlayMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val request: PlayRequest,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.PlaystateRequest
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class PlayStateMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val request: PlaystateRequest,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,18 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class RefreshProgressMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val data: Map<String, String>,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,15 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class RestartRequiredMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TaskResult
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ScheduledTaskEndedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: TaskResult,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TaskInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ScheduledTasksInfoMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: List<TaskInfo>,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,11 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("ScheduledTasksInfoStart")
|
||||
data class ScheduledTasksInfoStartMessage(
|
||||
@SerialName("Data")
|
||||
val period: PeriodicListenerPeriod = PeriodicListenerPeriod(),
|
||||
) : OutgoingSocketMessage
|
@ -0,0 +1,8 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("ScheduledTasksInfoStop")
|
||||
class ScheduledTasksInfoStopMessage : OutgoingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TimerEventInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SeriesTimerCancelledMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: TimerEventInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TimerEventInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SeriesTimerCreatedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: TimerEventInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,15 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ServerRestartingMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,15 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class ServerShuttingDownMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,18 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.SessionInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SessionsMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
@SerialName("Data")
|
||||
val Session: List<SessionInfo>,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,11 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("SessionsStart")
|
||||
data class SessionsStartMessage(
|
||||
@SerialName("Data")
|
||||
val period: PeriodicListenerPeriod = PeriodicListenerPeriod(),
|
||||
) : OutgoingSocketMessage
|
@ -0,0 +1,8 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("SessionsStart")
|
||||
class SessionsStopMessage : OutgoingSocketMessage
|
@ -0,0 +1,3 @@
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
interface SocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.SendCommand
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SyncPlayCommandMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val command: SendCommand,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.ObjectGroupUpdate
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SyncPlayGroupUpdateMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val update: ObjectGroupUpdate,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TimerEventInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class TimerCancelledMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: TimerEventInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.TimerEventInfo
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class TimerCreatedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val info: TimerEventInfo,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,20 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.UserItemDataDto
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class UserDataChangedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
@SerialName("UserId")
|
||||
val userId: UUID,
|
||||
@SerialName("UserDataList")
|
||||
val userDataList: List<UserItemDataDto>,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,18 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class UserDeletedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val userId: String,
|
||||
) : IncomingSocketMessage
|
@ -0,0 +1,19 @@
|
||||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package org.jellyfin.apiclient.model.socket
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.apiclient.model.api.UserDto
|
||||
import org.jellyfin.apiclient.model.serializer.UUIDSerializer
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class UserUpdatedMessage(
|
||||
@SerialName("MessageId")
|
||||
override val messageId: UUID,
|
||||
|
||||
@SerialName("Data")
|
||||
val user: UserDto,
|
||||
) : IncomingSocketMessage
|
Loading…
x
Reference in New Issue
Block a user