Merge pull request #111 from nielsvanvelzen/websockets

Add WebSockets
This commit is contained in:
Max Rumpf 2020-10-07 20:45:04 +02:00 committed by GitHub
commit 3c3fb57a59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1068 additions and 5 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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
)
)

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
package org.jellyfin.apiclient.model.socket
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("ActivityLogEntryStop")
class ActivityLogEntryStopMessage : OutgoingSocketMessage

View File

@ -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

View File

@ -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

View File

@ -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,
}

View File

@ -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
}

View File

@ -0,0 +1,8 @@
package org.jellyfin.apiclient.model.socket
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("KeepAlive")
class KeepAliveMessage : OutgoingSocketMessage

View File

@ -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

View File

@ -0,0 +1,4 @@
package org.jellyfin.apiclient.model.socket
interface OutgoingSocketMessage : SocketMessage

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())!!
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
package org.jellyfin.apiclient.model.socket
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("ScheduledTasksInfoStop")
class ScheduledTasksInfoStopMessage : OutgoingSocketMessage

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
package org.jellyfin.apiclient.model.socket
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("SessionsStart")
class SessionsStopMessage : OutgoingSocketMessage

View File

@ -0,0 +1,3 @@
package org.jellyfin.apiclient.model.socket
interface SocketMessage

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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