From a5f5bd9e0d42f1884bb061d8691600ee6840ded3 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Mon, 25 Dec 2023 23:44:43 +0100 Subject: [PATCH] Updates & fixes - Fix issue of sound being cut off when joining channel --- .idea/gradle.xml | 2 +- app/web/build.gradle.kts | 1 + bot/build.gradle.kts | 2 -- .../dev/schlaubi/tonbrett/bot/Plugin.kt | 6 +++++ .../tonbrett/bot/commands/PlayCommand.kt | 2 +- .../schlaubi/tonbrett/bot/core/SoundPlayer.kt | 4 +-- .../tonbrett/bot/server/FileServer.kt | 2 +- .../tonbrett/bot/server/SoundsRoute.kt | 25 ++++++++++++++++--- build.gradle.kts | 2 +- .../src/commonMain/kotlin/WebSocketRetry.kt | 8 +++--- common/build.gradle.kts | 1 + .../kotlin/dev/schlaubi/tonbrett/common/Id.kt | 25 ++++++++++++++++--- .../tonbrett/common/SerializersModule.kt | 4 ++- gradle/libs.versions.toml | 6 +++-- 14 files changed, 67 insertions(+), 23 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 406acec1..8f72cf8c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,7 +16,6 @@ + diff --git a/app/web/build.gradle.kts b/app/web/build.gradle.kts index 9e4544a1..0439b530 100644 --- a/app/web/build.gradle.kts +++ b/app/web/build.gradle.kts @@ -33,6 +33,7 @@ kotlin { @OptIn(ExperimentalComposeLibrary::class) implementation(compose.components.resources) implementation(compose.materialIconsExtended) + implementation(libs.ktor.sse) } } } diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts index 52e77dee..b2168ba4 100644 --- a/bot/build.gradle.kts +++ b/bot/build.gradle.kts @@ -1,6 +1,5 @@ import dev.schlaubi.mikbot.gradle.mikbot import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -import kotlin.io.path.div plugins { alias(libs.plugins.ksp) @@ -12,7 +11,6 @@ plugins { dependencies { implementation(projects.common) ktorDependency(libs.ktor.server.auth) - ktorDependency(libs.ktor.server.websockets) ktorDependency(libs.ktor.server.cors) ktorDependency(libs.ktor.server.auth.jwt) implementation(libs.kmongo.id.serialization) diff --git a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/Plugin.kt b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/Plugin.kt index e68a6e6f..b8b70b7c 100644 --- a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/Plugin.kt +++ b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/Plugin.kt @@ -8,9 +8,15 @@ import dev.schlaubi.mikbot.plugin.api.PluginMain import dev.schlaubi.mikbot.plugin.api.module.SubCommandModule import dev.schlaubi.tonbrett.bot.commands.* import dev.schlaubi.tonbrett.bot.core.VoiceStateWatcher +import dev.schlaubi.tonbrett.common.TonbrettSerializersModule +import org.litote.kmongo.serialization.registerModule @PluginMain class Plugin(context: PluginContext) : Plugin(context) { + override fun start() { + registerModule(TonbrettSerializersModule) + } + override fun ExtensibleBotBuilder.ExtensionsBuilder.addExtensions() { add(::Module) add(::VoiceStateWatcher) diff --git a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/commands/PlayCommand.kt b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/commands/PlayCommand.kt index 812192ab..c066d178 100644 --- a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/commands/PlayCommand.kt +++ b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/commands/PlayCommand.kt @@ -1,6 +1,6 @@ package dev.schlaubi.tonbrett.bot.commands -import dev.kord.rest.builder.message.create.actionRow +import dev.kord.rest.builder.message.actionRow import dev.schlaubi.mikbot.plugin.api.module.SubCommandModule import dev.schlaubi.mikbot.util_plugins.ktor.api.buildBotUrl import io.ktor.http.* diff --git a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/core/SoundPlayer.kt b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/core/SoundPlayer.kt index 808ff315..35ca3eec 100644 --- a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/core/SoundPlayer.kt +++ b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/core/SoundPlayer.kt @@ -9,7 +9,7 @@ import dev.schlaubi.tonbrett.bot.util.player import dev.schlaubi.tonbrett.common.InterfaceAvailabilityChangeEvent import dev.schlaubi.tonbrett.common.Snowflake import dev.schlaubi.tonbrett.common.Sound -import io.ktor.http.path +import io.ktor.http.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance @@ -63,7 +63,7 @@ class SoundPlayer(guild: GuildBehavior) : CoroutineScope { } @Suppress("INVISIBLE_MEMBER", "SuspendFunctionOnCoroutineScope") - suspend fun playSound(sound: Sound, user: Snowflake) { + suspend fun playSound(sound: Sound, user: Snowflake, channelId: Snowflake?) { val alreadyLocked = locked locked = true updateAvailability(false, sound, user) diff --git a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/FileServer.kt b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/FileServer.kt index 8ea67577..d3f00b44 100644 --- a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/FileServer.kt +++ b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/FileServer.kt @@ -20,7 +20,7 @@ fun Route.files() { val path = Config.SOUNDS_FOLDER / sound.fileName val contentType = contentTypeRaw?.let { ContentType.parse(contentTypeRaw) } - ?: ContentType.defaultForFile(path) + ?: ContentType.defaultForPath(path) val content = LocalFileContent( path.toFile(), diff --git a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/SoundsRoute.kt b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/SoundsRoute.kt index ced3f9d4..f4d77070 100644 --- a/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/SoundsRoute.kt +++ b/bot/src/main/kotlin/dev/schlaubi/tonbrett/bot/server/SoundsRoute.kt @@ -4,22 +4,30 @@ import com.kotlindiscord.kord.extensions.koin.KordExContext import dev.kord.common.annotation.KordExperimental import dev.kord.common.annotation.KordUnsafe import dev.kord.core.Kord +import dev.kord.core.event.guild.VoiceServerUpdateEvent import dev.schlaubi.lavakord.kord.connectAudio import dev.schlaubi.tonbrett.bot.core.soundPlayer import dev.schlaubi.tonbrett.bot.core.voiceState -import dev.schlaubi.tonbrett.bot.io.* +import dev.schlaubi.tonbrett.bot.io.SoundBoardDatabase +import dev.schlaubi.tonbrett.bot.io.findAllTags +import dev.schlaubi.tonbrett.bot.io.findById +import dev.schlaubi.tonbrett.bot.io.searchGrouped import dev.schlaubi.tonbrett.bot.util.badRequest import dev.schlaubi.tonbrett.bot.util.soundNotFound import dev.schlaubi.tonbrett.bot.util.translate import dev.schlaubi.tonbrett.common.Route.* -import dev.schlaubi.tonbrett.common.Route.Tags import dev.schlaubi.tonbrett.common.util.convertForNonJvmPlatforms import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.resources.* import io.ktor.server.response.* import io.ktor.server.routing.Route +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.withTimeout +import kotlin.time.Duration.Companion.seconds @OptIn(KordUnsafe::class, KordExperimental::class) fun Route.sounds() { @@ -56,10 +64,19 @@ fun Route.sounds() { @Suppress("INVISIBLE_MEMBER", "EQUALITY_NOT_APPLICABLE") if (player.channelId == null) { player.player.link.connectAudio(voiceState.channelId) - } else if (player.channelId != voiceState.channelId) { + withTimeout(5.seconds) { + kord.events + .filterIsInstance() + .filter { + it.guildId == player.player.guildId + } + // wait for "Connect event" + .first() + } + } else if (player.channelId != null && player.channelId != voiceState.channelId) { badRequest(call.translate("rest.errors.vc_mismatch")) } - player.playSound(sound, user) + player.playSound(sound, user, voiceState.channelId?.takeIf { player.channelId == null }) call.respond(HttpStatusCode.Accepted) } } diff --git a/build.gradle.kts b/build.gradle.kts index bf7aaf52..1b0e06b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { group = "dev.schlaubi.tonbrett" - version = "1.18.2" + version = "1.18.3" repositories { mavenCentral() diff --git a/client/src/commonMain/kotlin/WebSocketRetry.kt b/client/src/commonMain/kotlin/WebSocketRetry.kt index 1293ce84..a51a7bb9 100644 --- a/client/src/commonMain/kotlin/WebSocketRetry.kt +++ b/client/src/commonMain/kotlin/WebSocketRetry.kt @@ -21,7 +21,7 @@ private val LOG = KotlinLogging.logger { } * * @see retryingWebSocket */ -val WebSocketRetry = createClientPlugin("WebSocketRetry", { HttpRequestRetry.Configuration() }) {} +val WebSocketRetry = createClientPlugin("WebSocketRetry", { HttpRequestRetryConfig() }) {} /** * Exception used for logging WebSocket connection errors. @@ -45,12 +45,12 @@ suspend fun HttpClient.retryingWebSocket( private class WebSocketRetryContext( val client: HttpClient, - val config: HttpRequestRetry.Configuration, + val config: HttpRequestRetryConfig, val httpRequestBuilder: HttpRequestBuilder.() -> Unit, val handler: suspend DefaultClientWebSocketSession.() -> Unit ) { lateinit var session: DefaultClientWebSocketSession - private var delayContext: HttpRequestRetry.DelayContext? = null + private var delayContext: HttpRetryDelayContext? = null private var tries = 1 fun reset() { @@ -63,7 +63,7 @@ private class WebSocketRetryContext( suspend fun reconnect(e: Throwable, isRetry: Boolean = true) { if (!isRetry) { - delayContext = HttpRequestRetry.DelayContext( + delayContext = HttpRetryDelayContext( HttpRequestBuilder(), session.call.response, e diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 4858691c..80ab6d9a 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { dependencies { compileOnly(libs.kmongo.id.serialization) compileOnly(libs.kord.common) + compileOnly(libs.kmongo.serialization) } } } diff --git a/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/Id.kt b/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/Id.kt index 125a5b07..c44b57a9 100644 --- a/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/Id.kt +++ b/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/Id.kt @@ -1,5 +1,7 @@ package dev.schlaubi.tonbrett.common +import com.github.jershell.kbson.BsonEncoder +import com.github.jershell.kbson.BsonFlexibleDecoder import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -7,15 +9,20 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import org.bson.types.ObjectId +import org.litote.kmongo.id.toId import org.litote.kmongo.toId // d8 can't understand k2 meta yet, so we can't use type-aliases @Serializable(with = IdSerializer::class) +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") public actual interface Id : org.litote.kmongo.Id +@Serializable(with = IdSerializer::class) @JvmInline -private value class WrappedId(private val id: org.litote.kmongo.Id) : Id, +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") // because of the serializers semantics, this is irrelevant +internal value class WrappedId(private val id: org.litote.kmongo.Id) : Id, org.litote.kmongo.Id by id { override fun toString(): String = id.toString() } @@ -26,9 +33,19 @@ public object IdSerializer : KSerializer> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MongoID", PrimitiveKind.STRING) - override fun deserialize(decoder: Decoder): Id<*> = - WrappedId(decoder.decodeString().toId()) + override fun deserialize(decoder: Decoder): Id<*> { + return if (decoder is BsonFlexibleDecoder) { + WrappedId(decoder.reader.readObjectId().toId()) + } else { + WrappedId(decoder.decodeString().toId()) + } + } override fun serialize(encoder: Encoder, value: Id<*>): Unit = - encoder.encodeString(value.toString()) + if (encoder is BsonEncoder) { + val objectId = ObjectId(value.toString()) + encoder.encodeObjectId(objectId) + } else { + encoder.encodeString(value.toString()) + } } diff --git a/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/SerializersModule.kt b/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/SerializersModule.kt index aee1c8ac..b1334fae 100644 --- a/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/SerializersModule.kt +++ b/common/src/jvmMain/kotlin/dev/schlaubi/tonbrett/common/SerializersModule.kt @@ -1,8 +1,10 @@ package dev.schlaubi.tonbrett.common +import kotlinx.serialization.KSerializer import kotlinx.serialization.modules.SerializersModule -import org.litote.kmongo.id.StringId public actual val TonbrettSerializersModule: SerializersModule = SerializersModule { contextual(Id::class, IdSerializer) + @Suppress("UNCHECKED_CAST") // because of the serializers semantics, we know that this will work + contextual(WrappedId::class, IdSerializer as KSerializer>) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0b84aeb..44fe9214 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] kotlin = "2.0.0-Beta1" -ktor = "2.3.7" +ktor = "3.0.0-beta-1" kmongo = "4.10.0" kord = "0.12.0" -mikbot = "3.26.0" +mikbot = "3.27.0" ksp = "2.0.0-Beta1-1.0.14" kordex = "1.6.0-SNAPSHOT" android = "8.2.0" @@ -21,6 +21,7 @@ coil = "3.0.0-20231208.205440-1" [libraries] kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.6.1" } kmongo-id-serialization = { group = "org.litote.kmongo", name = "kmongo-id-serialization", version.ref = "kmongo" } +kmongo-serialization = { group = "org.litote.kmongo", name = "kmongo-serialization", version.ref = "kmongo" } kord-common = { group = "dev.kord", name = "kord-common", version.ref = "kord" } kordex-processor = { group = "com.kotlindiscord.kord.extensions", name = "annotation-processor", version.ref = "kordex" } @@ -45,6 +46,7 @@ ktor-client-winhttp = { group = "io.ktor", name = "ktor-client-winhttp", version ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" } +ktor-sse = { group = "io.ktor", name = "ktor-sse", version.ref = "ktor" } ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } mikbot-ktor = { group = "dev.schlaubi", name = "mikbot-ktor", version.ref = "mikbot" }