From 3785fdf193f155d90f798daede9312f2ace3d277 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Sun, 27 Apr 2025 12:55:09 +0200 Subject: [PATCH 01/13] Only use interaction callback response message as source if not ephemeral --- .../kotlin/at/xirado/jdui/view/metadata/MessageContext.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt b/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt index 2196f3f..efc9eed 100644 --- a/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt +++ b/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt @@ -1,7 +1,6 @@ package at.xirado.jdui.view.metadata import at.xirado.jdui.utils.await -import io.github.oshai.kotlinlogging.KotlinLogging import net.dv8tion.jda.api.JDA import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.entities.WebhookClient @@ -13,8 +12,6 @@ import okio.withLock import java.lang.ref.WeakReference import java.util.concurrent.locks.ReentrantLock -private val log = KotlinLogging.logger { } - internal class MessageContext( private val jda: WeakReference ) { @@ -34,7 +31,8 @@ internal class MessageContext( if (hook.hasCallbackResponse() && messageSource == null) { hook.callbackResponse.message?.let { - provideMessageSource(it.channelIdLong, it.idLong) + if (Message.MessageFlag.EPHEMERAL !in it.flags) + provideMessageSource(it.channelIdLong, it.idLong) } } } From 3549fbc162e7c326a2b54ea733f40fd44431b277 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Sun, 27 Apr 2025 12:56:24 +0200 Subject: [PATCH 02/13] Improve example logback.xml --- example/src/main/resources/logback.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main/resources/logback.xml b/example/src/main/resources/logback.xml index 8527d88..128e985 100644 --- a/example/src/main/resources/logback.xml +++ b/example/src/main/resources/logback.xml @@ -6,9 +6,9 @@ - + - + From 955311e7cad07825cec33d126a017a7ec95a436c Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Thu, 1 May 2025 14:05:05 +0200 Subject: [PATCH 03/13] Remove ViewMessageContainer.kt --- .../at/xirado/jdui/view/ViewMessageContainer.kt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 core/src/main/kotlin/at/xirado/jdui/view/ViewMessageContainer.kt diff --git a/core/src/main/kotlin/at/xirado/jdui/view/ViewMessageContainer.kt b/core/src/main/kotlin/at/xirado/jdui/view/ViewMessageContainer.kt deleted file mode 100644 index 0633b28..0000000 --- a/core/src/main/kotlin/at/xirado/jdui/view/ViewMessageContainer.kt +++ /dev/null @@ -1,12 +0,0 @@ -package at.xirado.jdui.view - -import at.xirado.jdui.state.ViewState -import at.xirado.jdui.view.definition.ViewDefinition - -class ViewMessageContainer internal constructor( - private val state: ViewState, - definition: ViewDefinition -) { - private val views = mutableListOf(definition) - -} \ No newline at end of file From 4b8d584dd43d0a5a50e7c0a65f63b665059cd1a0 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Thu, 1 May 2025 14:05:38 +0200 Subject: [PATCH 04/13] Add ViewState#toString --- core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt index f9a27a5..8b6ade2 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt @@ -182,6 +182,8 @@ class ViewState internal constructor( component.processInteraction(event) } + + override fun toString() = metadata.toString() } internal suspend fun createViewState( From 77e8a487c44aef5f55c5eea1c7235dbc23a28fed Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Mon, 5 May 2025 08:52:11 +0200 Subject: [PATCH 05/13] Start work on proper component callback handling --- .../src/main/kotlin/at/xirado/jdui/Context.kt | 13 +- .../at/xirado/jdui/component/Component.kt | 11 +- .../xirado/jdui/component/message/Button.kt | 13 +- .../jdui/component/message/SelectMenu.kt | 23 ++-- .../handler/ComponentInteractionHandler.kt | 125 ++---------------- .../kotlin/at/xirado/jdui/state/ViewState.kt | 32 +++-- .../interaction/ComponentCallbackResult.kt | 13 ++ .../interaction/ViewComponentInteraction.kt | 102 ++++++++++++++ .../jdui/view/metadata/ViewStateMetadata.kt | 19 +++ .../xirado/jdui/example/view/ComponentTest.kt | 2 +- .../xirado/jdui/example/view/CounterView.kt | 2 +- 11 files changed, 204 insertions(+), 151 deletions(-) create mode 100644 core/src/main/kotlin/at/xirado/jdui/state/interaction/ComponentCallbackResult.kt create mode 100644 core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt diff --git a/core/src/main/kotlin/at/xirado/jdui/Context.kt b/core/src/main/kotlin/at/xirado/jdui/Context.kt index 948965e..d75a939 100644 --- a/core/src/main/kotlin/at/xirado/jdui/Context.kt +++ b/core/src/main/kotlin/at/xirado/jdui/Context.kt @@ -79,12 +79,15 @@ class Context( return value } + fun copy(includeParent: Boolean = true): Context { + val parent = if (includeParent) this.parent?.copy() else null + val newContext = Context(parent) + newContext.provideAll(this) + return newContext + } + companion object { - fun copyOf(other: Context) = Context().apply { - other.lock.read { - context.putAll(other.context) - } - } + fun copyOf(other: Context, includeParent: Boolean = true) = other.copy(includeParent) } } diff --git a/core/src/main/kotlin/at/xirado/jdui/component/Component.kt b/core/src/main/kotlin/at/xirado/jdui/component/Component.kt index ef7991f..a7c6d48 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/Component.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/Component.kt @@ -1,11 +1,14 @@ package at.xirado.jdui.component +import at.xirado.jdui.state.interaction.ViewComponentInteraction import at.xirado.jdui.view.ViewDSL import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent import kotlin.reflect.KClass import kotlin.reflect.KType import net.dv8tion.jda.api.components.Component as JDAComponent +typealias ComponentCallback = suspend ViewComponentInteraction.() -> Unit + @ViewDSL abstract class Component { internal abstract val type: KType @@ -19,11 +22,11 @@ abstract class StatelessComponent : Component() { internal abstract fun buildComponent(uniqueId: Int): T } -abstract class StatefulActionComponent : StatefulComponent() { - internal abstract val callback: suspend E.() -> Unit - internal abstract val callbackClazz: KClass +abstract class StatefulActionComponent : StatefulComponent() { + internal abstract val callback: suspend ViewComponentInteraction.() -> Unit + internal abstract val eventClazz: KClass - internal abstract suspend fun processInteraction(event: GenericComponentInteractionCreateEvent) + internal abstract suspend fun processInteraction(interaction: ViewComponentInteraction) } abstract class ParentComponent : Component() { diff --git a/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt b/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt index 6cbaf13..c5ea600 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt @@ -1,11 +1,12 @@ package at.xirado.jdui.component.message +import at.xirado.jdui.component.ComponentCallback import at.xirado.jdui.component.StatefulActionComponent import at.xirado.jdui.component.StatelessComponent +import at.xirado.jdui.state.interaction.ViewComponentInteraction import net.dv8tion.jda.api.components.button.ButtonStyle import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent -import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent import kotlin.reflect.typeOf import net.dv8tion.jda.api.components.button.Button as JDAButton @@ -14,7 +15,7 @@ class ActionButton( var label: String?, var emoji: Emoji?, var disabled: Boolean, - override val callback: suspend ButtonInteractionEvent.() -> Unit, + override val callback: ComponentCallback, ) : StatefulActionComponent() { override fun buildComponent(id: String, uniqueId: Int): JDAButton { return JDAButton.of(style, id, label, emoji) @@ -22,12 +23,12 @@ class ActionButton( .withUniqueId(uniqueId) } - override suspend fun processInteraction(event: GenericComponentInteractionCreateEvent) { - callback(event as ButtonInteractionEvent) + override suspend fun processInteraction(interaction: ViewComponentInteraction) { + callback(interaction) } override val type = typeOf() - override val callbackClazz = ButtonInteractionEvent::class + override val eventClazz = ButtonInteractionEvent::class } class LinkButton( @@ -50,7 +51,7 @@ fun button( label: String? = null, emoji: Emoji? = null, disabled: Boolean = false, - callback: suspend ButtonInteractionEvent.() -> Unit, + callback: ComponentCallback, ): ActionButton { require(style != ButtonStyle.LINK) { "Cannot use ButtonStyle.LINK here. Use link() instead" } return ActionButton(style, label, emoji, disabled, callback) diff --git a/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt b/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt index 0d12e04..7ff4baa 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt @@ -1,10 +1,11 @@ package at.xirado.jdui.component.message +import at.xirado.jdui.component.ComponentCallback import at.xirado.jdui.component.StatefulActionComponent +import at.xirado.jdui.state.interaction.ViewComponentInteraction import net.dv8tion.jda.api.components.selects.SelectOption import net.dv8tion.jda.api.entities.channel.ChannelType import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent -import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent import kotlin.reflect.typeOf import net.dv8tion.jda.api.components.selects.EntitySelectMenu as JDAEntitySelectMenu @@ -18,7 +19,7 @@ class StringSelectMenu( var range: IntRange, var placeholder: String?, var disabled: Boolean, - override val callback: StringCallback + override val callback: ComponentCallback ) : StatefulActionComponent() { override fun buildComponent(id: String, uniqueId: Int): JDAStringSelectMenu { return JDAStringSelectMenu.create(id) @@ -30,12 +31,12 @@ class StringSelectMenu( .build() } - override suspend fun processInteraction(event: GenericComponentInteractionCreateEvent) { - callback(event as StringSelectInteractionEvent) + override suspend fun processInteraction(interaction: ViewComponentInteraction) { + callback(interaction) } override val type = typeOf() - override val callbackClazz = StringSelectInteractionEvent::class + override val eventClazz = StringSelectInteractionEvent::class } class EntitySelectMenu( @@ -44,7 +45,7 @@ class EntitySelectMenu( var range: IntRange, var placeholder: String?, var disabled: Boolean, - override val callback: EntityCallback, + override val callback: ComponentCallback, ) : StatefulActionComponent() { override fun buildComponent(id: String, uniqueId: Int): JDAEntitySelectMenu { return JDAEntitySelectMenu.create(id, targets) @@ -56,12 +57,12 @@ class EntitySelectMenu( .build() } - override suspend fun processInteraction(event: GenericComponentInteractionCreateEvent) { - callback(event as EntitySelectInteractionEvent) + override suspend fun processInteraction(interaction: ViewComponentInteraction) { + callback(interaction) } override val type = typeOf() - override val callbackClazz = EntitySelectInteractionEvent::class + override val eventClazz = EntitySelectInteractionEvent::class } fun stringSelect( @@ -69,7 +70,7 @@ fun stringSelect( range: IntRange = 1..1, placeholder: String? = null, disabled: Boolean = false, - callback: StringCallback + callback: ComponentCallback ): StringSelectMenu { return StringSelectMenu(options, range, placeholder, disabled, callback) } @@ -80,7 +81,7 @@ fun entitySelect( range: IntRange = 1..1, placeholder: String? = null, disabled: Boolean = false, - callback: EntityCallback + callback: ComponentCallback ) : EntitySelectMenu { return EntitySelectMenu(targets, channelTypes, range, placeholder, disabled, callback) } \ No newline at end of file diff --git a/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt b/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt index 2d60cd8..793edb2 100644 --- a/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt +++ b/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt @@ -1,25 +1,12 @@ package at.xirado.jdui.handler import at.xirado.jdui.JDUIListener -import at.xirado.jdui.component.message.container -import at.xirado.jdui.component.message.text -import at.xirado.jdui.crypto.decryptChaCha -import at.xirado.jdui.state.ViewState -import at.xirado.jdui.state.createViewState -import at.xirado.jdui.utils.decode -import at.xirado.jdui.utils.mergeCustomIds -import at.xirado.jdui.view.definition.function.view -import at.xirado.jdui.view.metadata.EncryptedViewStateMetadata -import at.xirado.jdui.view.populateMessageContext -import at.xirado.jdui.view.replyView +import at.xirado.jdui.state.interaction.ViewComponentInteraction import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.sync.withLock -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromByteArray -import kotlinx.serialization.protobuf.ProtoBuf +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent +import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent -import net.dv8tion.jda.api.utils.messages.MessageCreateData -import net.dv8tion.jda.api.utils.messages.MessageEditData +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent private val log = KotlinLogging.logger { } @@ -27,103 +14,15 @@ internal class ComponentInteractionHandler(private val jdui: JDUIListener) { private val config = jdui.config suspend fun handleComponentEvent(event: GenericComponentInteractionCreateEvent) { - val id = event.message.components.mergeCustomIds() + val now = System.currentTimeMillis() - when { - id.startsWith("j1:") -> handleStatefulView(id, event) - id.startsWith("j2:") -> handleStatelessView(id, event) - else -> return - } - } - - @OptIn(ExperimentalSerializationApi::class) - private suspend fun handleStatefulView(componentId: String, event: GenericComponentInteractionCreateEvent) { - val data = componentId.substringAfter("j1:") - - val decodedData = decode(data) - - val metadataEncrypted: EncryptedViewStateMetadata = ProtoBuf.decodeFromByteArray(decodedData) - - val secret = config.secret - val metadata = metadataEncrypted.decrypt(secret) - val id = metadata.id - - log.debug { "Handling component interaction for view $id" } - val cachedState = jdui.messageCache.getIfPresent(id) - - if (cachedState != null) { - val message = updateMessage(event, cachedState) - return event.editMessage(MessageEditData.fromCreateData(message)) - .populateMessageContext(cachedState.messageContext) - .queue() - } - - if (metadata.metadata.sourceData == null) { - return event.replyView(view { - compose { - +container(accentColor = 0xFF0000) { - +text("This action timed out!") - } - } - }, ephemeral = true).queue() - } - - val state = createViewState(jdui, metadata) - - val message = updateMessage(event, state) - event.editMessage(MessageEditData.fromCreateData(message)) - .populateMessageContext(state.messageContext) - .queue() - } - - @OptIn(ExperimentalSerializationApi::class) - private suspend fun handleStatelessView(componentId: String, event: GenericComponentInteractionCreateEvent) { - val id = componentId.substringAfter("j2:").toLong() - val cachedState = jdui.messageCache.getIfPresent(id) - - log.debug { "Handling component interaction for view $id" } - if (cachedState != null) { - val message = updateMessage(event, cachedState) - return event.editMessage(MessageEditData.fromCreateData(message)) - .populateMessageContext(cachedState.messageContext) - .queue() - } - - log.debug { "Getting state from db: $id" } - - val persistence = jdui.config.persistenceConfig - ?: throw IllegalStateException("No PersistenceConfig was provided!") - - val retrievedState = persistence.retrieveState(id) - ?: throw IllegalStateException("No such view with id $id") - - val secret = config.secret - val encryptedMetadata: EncryptedViewStateMetadata = ProtoBuf.decodeFromByteArray(retrievedState.data) - - val metadata = encryptedMetadata.decrypt(secret) - - if (metadata.metadata.sourceData == null) { - return event.replyView(view { - compose { - +container(accentColor = 0xFF0000) { - +text("This action timed out!") - } - } - }, ephemeral = true).queue() - } - - val state = createViewState(jdui, metadata) - val message = updateMessage(event, state) - - event.editMessage(MessageEditData.fromCreateData(message)) - .populateMessageContext(state.messageContext) - .queue() - } + val interaction = when (event) { + is ButtonInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) + is StringSelectInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) + is EntitySelectInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) + else -> TODO("Unsupported") + } ?: return - private suspend fun updateMessage(event: GenericComponentInteractionCreateEvent, state: ViewState): MessageCreateData { - return state.mutex.withLock { - state.handleComponentInteraction(event) - state.composeMessage() - } + interaction.process() } } \ No newline at end of file diff --git a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt index 8b6ade2..29c10c8 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt @@ -4,15 +4,10 @@ import at.xirado.jdui.Context import at.xirado.jdui.JDUIListener import at.xirado.jdui.component.* import at.xirado.jdui.config.ViewData -import at.xirado.jdui.crypto.encryptChaCha import at.xirado.jdui.utils.encode import at.xirado.jdui.utils.splitIntoParts -import at.xirado.jdui.utils.toBytes import at.xirado.jdui.view.definition.ViewDefinition import at.xirado.jdui.view.metadata.* -import at.xirado.jdui.view.metadata.EncryptedViewStateMetadata -import at.xirado.jdui.view.metadata.MessageContext -import at.xirado.jdui.view.metadata.ViewStateMetadata import at.xirado.jdui.view.metadata.source.ClassViewSourceData import at.xirado.jdui.view.metadata.source.FunctionViewSourceData import at.xirado.jdui.view.metadata.source.ViewSourceCache @@ -164,7 +159,8 @@ class ViewState internal constructor( return userState.setUserState(property, value) } - internal suspend fun handleComponentInteraction(event: GenericComponentInteractionCreateEvent) { + @Suppress("UNCHECKED_CAST") + internal suspend fun getActionComponentFromEvent(event: E): StatefulActionComponent<*, E> { val uniqueId = event.uniqueId val componentIndex = getComponentIndex() @@ -175,16 +171,32 @@ class ViewState internal constructor( throw IllegalStateException("Component is not an action component. Broken view state?") val thisEventClazz = event::class - val componentEventClazz = component.callbackClazz + val componentEventClazz = component.eventClazz if (thisEventClazz != componentEventClazz) throw IllegalStateException("Expected event of type $componentEventClazz but got $thisEventClazz. Broken view state?") - component.processInteraction(event) + return component as StatefulActionComponent<*, E> } override fun toString() = metadata.toString() -} +} + +internal suspend fun getViewStateByDiscordCustomIds(jdui: JDUIListener, mergedCustomIds: String): ViewState? { + return if (mergedCustomIds.startsWith("j2:")) { + val id = mergedCustomIds.removePrefix("j2:").toLong() + jdui.messageCache.getIfPresent(id)?.let { return it } + val metadata = retrieveViewMetadataFromDb(jdui, id) + createViewState(jdui, metadata.decrypt(jdui.config.secret)) + } else if (mergedCustomIds.startsWith("j1:")) { + val data = mergedCustomIds.removePrefix("j1:") + val encryptedMetadata = decodeViewState(data) + jdui.messageCache.getIfPresent(encryptedMetadata.id)?.let { return it } + createViewState(jdui, encryptedMetadata.decrypt(jdui.config.secret)) + } else { + null + } +} internal suspend fun createViewState( jdui: JDUIListener, @@ -193,7 +205,7 @@ internal suspend fun createViewState( context: Context? = null, ): ViewState { val sourceData = metadata.metadata.sourceData - ?: throw IllegalStateException("Metadata does not contain SourceData!") + ?: throw IllegalStateException("Cannot create view state with no sourceData!") val source = ViewSourceCache.getViewSource(sourceData) val definition = source.create() diff --git a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ComponentCallbackResult.kt b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ComponentCallbackResult.kt new file mode 100644 index 0000000..1639e09 --- /dev/null +++ b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ComponentCallbackResult.kt @@ -0,0 +1,13 @@ +package at.xirado.jdui.state.interaction + +import net.dv8tion.jda.api.utils.messages.MessageCreateData + +sealed interface ComponentCallbackResult + +// Update the message (default behaviour) +object UpdateMessage : ComponentCallbackResult +// Do not respond to the interaction +object DoNothing : ComponentCallbackResult + +class SendFollowup(val function: suspend () -> MessageCreateData, val ephemeral: Boolean) : ComponentCallbackResult + diff --git a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt new file mode 100644 index 0000000..6c96680 --- /dev/null +++ b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt @@ -0,0 +1,102 @@ +package at.xirado.jdui.state.interaction + +import at.xirado.jdui.Context +import at.xirado.jdui.JDUIListener +import at.xirado.jdui.component.StatefulActionComponent +import at.xirado.jdui.state.ViewState +import at.xirado.jdui.state.getViewStateByDiscordCustomIds +import at.xirado.jdui.utils.await +import at.xirado.jdui.utils.mergeCustomIds +import at.xirado.jdui.view.ViewDSL +import at.xirado.jdui.view.createFunctionViewState +import at.xirado.jdui.view.definition.function.ViewDefinitionFunction +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withLock +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent +import net.dv8tion.jda.api.utils.messages.MessageCreateData +import net.dv8tion.jda.api.utils.messages.MessageEditData +import kotlin.reflect.KFunction + +@ViewDSL +class ViewComponentInteraction private constructor( + internal val state: ViewState, + val event: E, + internal val receivedTimestamp: Long, +) { + private val mutex = state.mutex + private var isInitialized = false + + internal var result: ComponentCallbackResult = UpdateMessage + + private suspend fun initialize(): StatefulActionComponent<*, E> { + val component = getComponentFromEvent() + isInitialized = true + return component + } + + internal suspend fun process() = mutex.withLock { + if (isInitialized) + throw IllegalStateException("Can only call processEvent() once!") + + val component = initialize() + component.processInteraction(this) + val result = this.result + + when (result) { + is UpdateMessage -> { + val message = state.composeMessage() + event.editMessage(MessageEditData.fromCreateData(message)).queue() + } + is SendFollowup -> { + val ephemeral = result.ephemeral + event.deferReply(ephemeral).queue() + state.coroutineScope.launch { + val function = result.function + val messageData = function() + event.hook.sendMessage(messageData).await() + } + } + is DoNothing -> {} + else -> TODO("Unsupported") + } + } + + fun doNothing() { + result = DoNothing + } + + private suspend fun getComponentFromEvent() = state.getActionComponentFromEvent(event) + + internal companion object { + internal suspend fun fromEvent( + jdui: JDUIListener, + event: E, + receivedTimestamp: Long, + ): ViewComponentInteraction? { + val id = event.message.components.mergeCustomIds() + val state = getViewStateByDiscordCustomIds(jdui, id) ?: return null + + return ViewComponentInteraction(state, event, receivedTimestamp) + } + } +} + +fun ViewComponentInteraction<*>.sendFollowup( + function: KFunction, + ephemeral: Boolean = true, + inheritContext: Boolean = false, + context: Context? = null, +) { + val messageFunction: suspend () -> MessageCreateData = { + val stateContext = when { + inheritContext && context == null -> Context.copyOf(state.context) + inheritContext && context != null -> Context.copyOf(state.context).also { it.provideAll(context) } + else -> context + } + + val state = createFunctionViewState(event.jda, function, stateContext) + state.composeMessage() + } + + result = SendFollowup(messageFunction, ephemeral) +} diff --git a/core/src/main/kotlin/at/xirado/jdui/view/metadata/ViewStateMetadata.kt b/core/src/main/kotlin/at/xirado/jdui/view/metadata/ViewStateMetadata.kt index fee68a4..b0e48c9 100644 --- a/core/src/main/kotlin/at/xirado/jdui/view/metadata/ViewStateMetadata.kt +++ b/core/src/main/kotlin/at/xirado/jdui/view/metadata/ViewStateMetadata.kt @@ -1,8 +1,10 @@ package at.xirado.jdui.view.metadata +import at.xirado.jdui.JDUIListener import at.xirado.jdui.config.Secret import at.xirado.jdui.crypto.decryptChaCha import at.xirado.jdui.crypto.encryptChaCha +import at.xirado.jdui.utils.decode import at.xirado.jdui.utils.toBytes import at.xirado.jdui.view.metadata.source.ViewSourceData import kotlinx.serialization.ExperimentalSerializationApi @@ -55,3 +57,20 @@ internal class DecryptedViewStateMetadata( } } +@OptIn(ExperimentalSerializationApi::class) +internal fun decodeViewState(encodedData: String): EncryptedViewStateMetadata { + val decodedData = decode(encodedData) + return ProtoBuf.decodeFromByteArray(decodedData) +} + +@OptIn(ExperimentalSerializationApi::class) +internal suspend fun retrieveViewMetadataFromDb(jdui: JDUIListener, id: Long): EncryptedViewStateMetadata { + val persistence = jdui.config.persistenceConfig + ?: throw IllegalStateException("JDUI does not have a PersistenceConfig!") + + val retrievedState = persistence.retrieveState(id) + ?: throw IllegalStateException("No such view with id $id") + + return ProtoBuf.decodeFromByteArray(retrievedState.data) +} + diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt index eae3236..0f40992 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt @@ -68,7 +68,7 @@ fun componentTest() = view { +row { val options = emojis.map { SelectOption.of(it.key, it.value).withEmoji(Emoji.fromUnicode(it.value)) } +stringSelect(options, placeholder = "Select an emoji") { - selectedOptions.forEach { emojiString += it.value } + event.selectedOptions.forEach { emojiString += it.value } } } +row { diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt index 5957e41..d39b583 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt @@ -30,7 +30,7 @@ fun counterView() = view { +row { +button(ButtonStyle.SECONDARY, "Increment by $step") { counter += step - lastUpdate = LastUpdate(user.name, step) + lastUpdate = LastUpdate(event.user.name, step) } +button(ButtonStyle.PRIMARY, "-1", disabled = step == 1) { From d7c8546f3564436d72e361b02d37d0e78211d1b1 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Fri, 16 May 2025 20:23:05 +0200 Subject: [PATCH 06/13] Update JDA CV2 branch --- .../main/kotlin/at/xirado/jdui/component/message/Button.kt | 4 ++-- .../kotlin/at/xirado/jdui/component/message/SelectMenu.kt | 6 +++--- .../kotlin/at/xirado/jdui/component/message/TextDisplay.kt | 2 +- core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt | 2 +- .../src/main/kotlin/at/xirado/jdui/example/view/CatView.kt | 2 +- .../kotlin/at/xirado/jdui/example/view/ComponentTest.kt | 6 +++--- .../main/kotlin/at/xirado/jdui/example/view/CounterView.kt | 2 +- .../kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt | 2 +- .../main/kotlin/at/xirado/jdui/example/view/StateTest.kt | 2 +- .../at/xirado/jdui/example/view/UrbanDictionaryView.kt | 2 +- gradle/libs.versions.toml | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt b/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt index c5ea600..209113c 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/message/Button.kt @@ -4,11 +4,11 @@ import at.xirado.jdui.component.ComponentCallback import at.xirado.jdui.component.StatefulActionComponent import at.xirado.jdui.component.StatelessComponent import at.xirado.jdui.state.interaction.ViewComponentInteraction -import net.dv8tion.jda.api.components.button.ButtonStyle +import net.dv8tion.jda.api.components.buttons.ButtonStyle import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent import kotlin.reflect.typeOf -import net.dv8tion.jda.api.components.button.Button as JDAButton +import net.dv8tion.jda.api.components.buttons.Button as JDAButton class ActionButton( var style: ButtonStyle, diff --git a/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt b/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt index 7ff4baa..f4dd5a7 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/message/SelectMenu.kt @@ -3,13 +3,13 @@ package at.xirado.jdui.component.message import at.xirado.jdui.component.ComponentCallback import at.xirado.jdui.component.StatefulActionComponent import at.xirado.jdui.state.interaction.ViewComponentInteraction -import net.dv8tion.jda.api.components.selects.SelectOption +import net.dv8tion.jda.api.components.selections.SelectOption import net.dv8tion.jda.api.entities.channel.ChannelType import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent import kotlin.reflect.typeOf -import net.dv8tion.jda.api.components.selects.EntitySelectMenu as JDAEntitySelectMenu -import net.dv8tion.jda.api.components.selects.StringSelectMenu as JDAStringSelectMenu +import net.dv8tion.jda.api.components.selections.EntitySelectMenu as JDAEntitySelectMenu +import net.dv8tion.jda.api.components.selections.StringSelectMenu as JDAStringSelectMenu private typealias StringCallback = suspend StringSelectInteractionEvent.() -> Unit private typealias EntityCallback = suspend EntitySelectInteractionEvent.() -> Unit diff --git a/core/src/main/kotlin/at/xirado/jdui/component/message/TextDisplay.kt b/core/src/main/kotlin/at/xirado/jdui/component/message/TextDisplay.kt index 2857cca..f0e09e8 100644 --- a/core/src/main/kotlin/at/xirado/jdui/component/message/TextDisplay.kt +++ b/core/src/main/kotlin/at/xirado/jdui/component/message/TextDisplay.kt @@ -8,7 +8,7 @@ class TextDisplay( var content: String ) : StatelessComponent() { override fun buildComponent(uniqueId: Int): JDATextDisplay { - return JDATextDisplay.create(content).withUniqueId(uniqueId) + return JDATextDisplay.of(content).withUniqueId(uniqueId) } override val type = typeOf() diff --git a/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt b/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt index 05147c2..cb3c172 100644 --- a/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt +++ b/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt @@ -3,7 +3,7 @@ package at.xirado.jdui.utils import net.dv8tion.jda.api.components.ActionComponent import net.dv8tion.jda.api.components.Component import net.dv8tion.jda.api.components.actionrow.ActionRow -import net.dv8tion.jda.api.components.button.Button +import net.dv8tion.jda.api.components.buttons.Button import net.dv8tion.jda.api.components.container.Container import net.dv8tion.jda.api.components.section.Section diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt index 1dada14..4416ca0 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt @@ -14,7 +14,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream -import net.dv8tion.jda.api.components.button.ButtonStyle.SECONDARY +import net.dv8tion.jda.api.components.buttons.ButtonStyle.SECONDARY import net.dv8tion.jda.api.components.mediagallery.MediaGalleryItem import okhttp3.OkHttpClient import okhttp3.Request diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt index 0f40992..2d0362a 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt @@ -4,10 +4,10 @@ import at.xirado.jdui.component.message.* import at.xirado.jdui.component.row import at.xirado.jdui.state.state import at.xirado.jdui.view.definition.function.view -import net.dv8tion.jda.api.components.button.ButtonStyle +import net.dv8tion.jda.api.components.buttons.ButtonStyle import net.dv8tion.jda.api.components.mediagallery.MediaGalleryItem -import net.dv8tion.jda.api.components.selects.EntitySelectMenu -import net.dv8tion.jda.api.components.selects.SelectOption +import net.dv8tion.jda.api.components.selections.EntitySelectMenu +import net.dv8tion.jda.api.components.selections.SelectOption import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.utils.FileUpload diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt index d39b583..673ee8a 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt @@ -6,7 +6,7 @@ import at.xirado.jdui.component.row import at.xirado.jdui.state.state import at.xirado.jdui.view.definition.function.view import kotlinx.serialization.Serializable -import net.dv8tion.jda.api.components.button.ButtonStyle +import net.dv8tion.jda.api.components.buttons.ButtonStyle @Serializable data class LastUpdate( diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt index 8a8bd92..aefff09 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt @@ -10,7 +10,7 @@ import at.xirado.jdui.view.View import at.xirado.jdui.view.compose import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import net.dv8tion.jda.api.components.button.ButtonStyle +import net.dv8tion.jda.api.components.buttons.ButtonStyle import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.utils.TimeFormat diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt index 7081887..6a11f43 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt @@ -6,7 +6,7 @@ import at.xirado.jdui.component.message.text import at.xirado.jdui.component.row import at.xirado.jdui.state.state import at.xirado.jdui.view.definition.function.view -import net.dv8tion.jda.api.components.button.ButtonStyle.* +import net.dv8tion.jda.api.components.buttons.ButtonStyle.* fun stateTest() = view { var someInt by state(0) diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt index f735ce4..82c78a0 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt @@ -9,7 +9,7 @@ import dev.minn.jda.ktx.util.await import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import net.dv8tion.jda.api.components.button.ButtonStyle +import net.dv8tion.jda.api.components.buttons.ButtonStyle import net.dv8tion.jda.api.components.separator.Separator import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.utils.TimeFormat diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 10a8dbd..216b00f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ publishing = "0.31.0" kotlin-coroutines = "1.10.1" kotlin-protobuf = "1.8.1" -jda = "54038f1cec" +jda = "4a9d724a21" slf4j-api = "2.0.13" kotlin-logging = "5.1.0" snowflake-id = "0.0.2" From 7d49b5aed565eb9a9937a52f50587e5d691c03bd Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Wed, 7 May 2025 12:56:41 +0200 Subject: [PATCH 07/13] populate MessageContext on component interactions --- core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt | 2 +- .../jdui/state/interaction/ViewComponentInteraction.kt | 8 +++++++- .../at/xirado/jdui/view/metadata/MessageContext.kt | 9 +++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt index 29c10c8..9f65a2b 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt @@ -36,7 +36,7 @@ class ViewState internal constructor( internal var componentIndex: ComponentIndex? = null internal var mutex = Mutex() internal val context = Context(listener.context) - internal val messageContext = MessageContext(WeakReference(listener.jda)) + internal val messageContext = MessageContext(this) private var userState = metadata.metadata.sourceData?.let { if (supportUserState) UserStateCollection(metadata.metadata.userState) else null diff --git a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt index 6c96680..7fd2f41 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt @@ -10,6 +10,7 @@ import at.xirado.jdui.utils.mergeCustomIds import at.xirado.jdui.view.ViewDSL import at.xirado.jdui.view.createFunctionViewState import at.xirado.jdui.view.definition.function.ViewDefinitionFunction +import at.xirado.jdui.view.populateMessageContext import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent @@ -39,13 +40,18 @@ class ViewComponentInteraction privat throw IllegalStateException("Can only call processEvent() once!") val component = initialize() + + state.messageContext.provideInteractionHook(event.hook) + component.processInteraction(this) val result = this.result when (result) { is UpdateMessage -> { val message = state.composeMessage() - event.editMessage(MessageEditData.fromCreateData(message)).queue() + event.editMessage(MessageEditData.fromCreateData(message)) + .populateMessageContext(state.messageContext) + .queue() } is SendFollowup -> { val ephemeral = result.ephemeral diff --git a/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt b/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt index efc9eed..cb1b886 100644 --- a/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt +++ b/core/src/main/kotlin/at/xirado/jdui/view/metadata/MessageContext.kt @@ -1,7 +1,8 @@ package at.xirado.jdui.view.metadata +import at.xirado.jdui.state.ViewState import at.xirado.jdui.utils.await -import net.dv8tion.jda.api.JDA +import io.github.oshai.kotlinlogging.KotlinLogging import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.entities.WebhookClient import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel @@ -9,12 +10,12 @@ import net.dv8tion.jda.api.interactions.InteractionHook import net.dv8tion.jda.api.requests.RestAction import net.dv8tion.jda.api.utils.messages.MessageEditData import okio.withLock -import java.lang.ref.WeakReference import java.util.concurrent.locks.ReentrantLock internal class MessageContext( - private val jda: WeakReference + private val viewState: ViewState, ) { + private val jda = viewState.listener.jda private val lock = ReentrantLock() private var webhook: WebhookMessageSource? = null @@ -86,7 +87,7 @@ internal class MessageContext( messageData: MessageEditData ): RestAction? { val messageSource = this.messageSource ?: return null - val jda = this.jda.get() ?: return null + val jda = this.jda val channel = jda.getChannelById(MessageChannel::class.java, messageSource.channelId) ?: return null From cac9eff07a30a286e48e06cc0e5261327ee6643a Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Sun, 18 May 2025 16:39:48 +0200 Subject: [PATCH 08/13] Add webhook support --- .../main/kotlin/at/xirado/jdui/view/JDA.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/core/src/main/kotlin/at/xirado/jdui/view/JDA.kt b/core/src/main/kotlin/at/xirado/jdui/view/JDA.kt index 11e75f7..1efe1ab 100644 --- a/core/src/main/kotlin/at/xirado/jdui/view/JDA.kt +++ b/core/src/main/kotlin/at/xirado/jdui/view/JDA.kt @@ -7,10 +7,12 @@ import at.xirado.jdui.view.definition.ViewDefinition import at.xirado.jdui.view.definition.function.ViewDefinitionFunction import at.xirado.jdui.view.metadata.MessageContext import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.entities.WebhookClient import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel import net.dv8tion.jda.api.interactions.InteractionHook import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback import net.dv8tion.jda.api.requests.RestAction +import net.dv8tion.jda.api.utils.messages.MessageCreateData import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -90,6 +92,38 @@ suspend fun MessageChannel.sendView(definition: ViewDefinition, context: Context return sendMessage(message).populateMessageContext(messageContext) } +// Webhooks + +suspend fun WebhookClient.sendView( + function: KFunction, + context: Context? = null, +): RestAction { + val state = createFunctionViewState(jda, function, context) + val messageContext = state.messageContext + + val message = state.composeMessage() + return sendMessage(message).useComponentsV2().populateMessageContext(this, messageContext) +} + +suspend inline fun WebhookClient.sendView(context: Context? = null) = sendView(T::class, context) + +suspend fun WebhookClient.sendView(clazz: KClass, context: Context? = null): RestAction { + val state = createClassViewState(jda, clazz, context) + val messageContext = state.messageContext + + val message: MessageCreateData = state.composeMessage() + return sendMessage(message).useComponentsV2().populateMessageContext(this, messageContext) +} + +suspend fun WebhookClient.sendView(definition: ViewDefinition, context: Context? = null): RestAction { + val instance = getInstance(jda) + val state = createViewState(instance, definition, context = context) + val messageContext = state.messageContext + + val message = state.composeMessage() + return sendMessage(message).useComponentsV2().populateMessageContext(this, messageContext) +} + @JvmName("populateMessageContextInteractionHook") internal fun RestAction.populateMessageContext(context: MessageContext) = onSuccess { context.provideInteractionHook(it) @@ -98,4 +132,14 @@ internal fun RestAction.populateMessageContext(context: Message @JvmName("populateMessageContextInteractionMessage") private fun RestAction.populateMessageContext(context: MessageContext) = onSuccess { context.provideMessageSource(it.channelIdLong, it.idLong) +} + +@JvmName("populateMessageContextWebhookMessage") +private fun RestAction.populateMessageContext(webhookClient: WebhookClient, context: MessageContext) = onSuccess { + if (webhookClient is InteractionHook) + context.provideInteractionHook(webhookClient) + else + context.provideWebhookClient(webhookClient, it.idLong) + +// context.provideMessageSource(it.channelIdLong, it.idLong) } \ No newline at end of file From 373e855fe4bbca303dd68cdca4b13125f8f0fdf1 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Mon, 26 May 2025 16:00:27 +0200 Subject: [PATCH 09/13] Make base65536 functions internal and add DecodeException --- .../kotlin/at/xirado/jdui/utils/Base65536.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/utils/Base65536.kt b/core/src/main/kotlin/at/xirado/jdui/utils/Base65536.kt index 356f618..6949644 100644 --- a/core/src/main/kotlin/at/xirado/jdui/utils/Base65536.kt +++ b/core/src/main/kotlin/at/xirado/jdui/utils/Base65536.kt @@ -10,7 +10,7 @@ private val B2: Map = mapOf( 5376 to -1, 13312 to 0, 13568 to 1, 13824 to 2, 14080 to 3, 14336 to 4, 14592 to 5, 14848 to 6, 15104 to 7, 15360 to 8, 15616 to 9, 15872 to 10, 16128 to 11, 16384 to 12, 16640 to 13, 16896 to 14, 17152 to 15, 17408 to 16, 17664 to 17, 17920 to 18, 18176 to 19, 18432 to 20, 18688 to 21, 18944 to 22, 19200 to 23, 19456 to 24, 19968 to 25, 20224 to 26, 20480 to 27, 20736 to 28, 20992 to 29, 21248 to 30, 21504 to 31, 21760 to 32, 22016 to 33, 22272 to 34, 22528 to 35, 22784 to 36, 23040 to 37, 23296 to 38, 23552 to 39, 23808 to 40, 24064 to 41, 24320 to 42, 24576 to 43, 24832 to 44, 25088 to 45, 25344 to 46, 25600 to 47, 25856 to 48, 26112 to 49, 26368 to 50, 26624 to 51, 26880 to 52, 27136 to 53, 27392 to 54, 27648 to 55, 27904 to 56, 28160 to 57, 28416 to 58, 28672 to 59, 28928 to 60, 29184 to 61, 29440 to 62, 29696 to 63, 29952 to 64, 30208 to 65, 30464 to 66, 30720 to 67, 30976 to 68, 31232 to 69, 31488 to 70, 31744 to 71, 32000 to 72, 32256 to 73, 32512 to 74, 32768 to 75, 33024 to 76, 33280 to 77, 33536 to 78, 33792 to 79, 34048 to 80, 34304 to 81, 34560 to 82, 34816 to 83, 35072 to 84, 35328 to 85, 35584 to 86, 35840 to 87, 36096 to 88, 36352 to 89, 36608 to 90, 36864 to 91, 37120 to 92, 37376 to 93, 37632 to 94, 37888 to 95, 38144 to 96, 38400 to 97, 38656 to 98, 38912 to 99, 39168 to 100, 39424 to 101, 39680 to 102, 39936 to 103, 40192 to 104, 40448 to 105, 41216 to 106, 41472 to 107, 41728 to 108, 42240 to 109, 67072 to 110, 73728 to 111, 73984 to 112, 74240 to 113, 77824 to 114, 78080 to 115, 78336 to 116, 78592 to 117, 82944 to 118, 83200 to 119, 92160 to 120, 92416 to 121, 131072 to 122, 131328 to 123, 131584 to 124, 131840 to 125, 132096 to 126, 132352 to 127, 132608 to 128, 132864 to 129, 133120 to 130, 133376 to 131, 133632 to 132, 133888 to 133, 134144 to 134, 134400 to 135, 134656 to 136, 134912 to 137, 135168 to 138, 135424 to 139, 135680 to 140, 135936 to 141, 136192 to 142, 136448 to 143, 136704 to 144, 136960 to 145, 137216 to 146, 137472 to 147, 137728 to 148, 137984 to 149, 138240 to 150, 138496 to 151, 138752 to 152, 139008 to 153, 139264 to 154, 139520 to 155, 139776 to 156, 140032 to 157, 140288 to 158, 140544 to 159, 140800 to 160, 141056 to 161, 141312 to 162, 141568 to 163, 141824 to 164, 142080 to 165, 142336 to 166, 142592 to 167, 142848 to 168, 143104 to 169, 143360 to 170, 143616 to 171, 143872 to 172, 144128 to 173, 144384 to 174, 144640 to 175, 144896 to 176, 145152 to 177, 145408 to 178, 145664 to 179, 145920 to 180, 146176 to 181, 146432 to 182, 146688 to 183, 146944 to 184, 147200 to 185, 147456 to 186, 147712 to 187, 147968 to 188, 148224 to 189, 148480 to 190, 148736 to 191, 148992 to 192, 149248 to 193, 149504 to 194, 149760 to 195, 150016 to 196, 150272 to 197, 150528 to 198, 150784 to 199, 151040 to 200, 151296 to 201, 151552 to 202, 151808 to 203, 152064 to 204, 152320 to 205, 152576 to 206, 152832 to 207, 153088 to 208, 153344 to 209, 153600 to 210, 153856 to 211, 154112 to 212, 154368 to 213, 154624 to 214, 154880 to 215, 155136 to 216, 155392 to 217, 155648 to 218, 155904 to 219, 156160 to 220, 156416 to 221, 156672 to 222, 156928 to 223, 157184 to 224, 157440 to 225, 157696 to 226, 157952 to 227, 158208 to 228, 158464 to 229, 158720 to 230, 158976 to 231, 159232 to 232, 159488 to 233, 159744 to 234, 160000 to 235, 160256 to 236, 160512 to 237, 160768 to 238, 161024 to 239, 161280 to 240, 161536 to 241, 161792 to 242, 162048 to 243, 162304 to 244, 162560 to 245, 162816 to 246, 163072 to 247, 163328 to 248, 163584 to 249, 163840 to 250, 164096 to 251, 164352 to 252, 164608 to 253, 164864 to 254, 165120 to 255 ) -fun encode(input: ByteArray): String { +internal fun encode(input: ByteArray): String { val writer = StringBuilder() val length = input.size var x = 0 @@ -25,22 +25,26 @@ fun encode(input: ByteArray): String { return writer.toString() } -fun decode(input: String): ByteArray { +internal fun decode(input: String): ByteArray { val outputStream = ByteArrayOutputStream() var done = false val codePoints = input.codePoints() for (codePoint in codePoints) { val b1 = codePoint and 0xFF - val b2 = B2[codePoint - b1] ?: throw IllegalArgumentException("Invalid base65536 code point: $codePoint") + val b2 = B2[codePoint - b1] ?: throw DecodeException("Invalid base65536 code point: $codePoint") val bytes = if (b2 == -1) byteArrayOf(b1.toByte()) else byteArrayOf(b1.toByte(), b2.toByte()) if (bytes.size == 1) { - if (done) { - throw IllegalArgumentException("base65536 sequence continued after final byte") - } + if (done) + throw DecodeException("base65536 sequence continued after final byte") done = true } outputStream.write(bytes) } return outputStream.toByteArray() -} \ No newline at end of file +} + +class DecodeException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) \ No newline at end of file From 1fa8013c8382980f93eda0ac925bb61f22f1cc32 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Mon, 26 May 2025 16:02:53 +0200 Subject: [PATCH 10/13] Optimize component interaction handling and custom id parsing --- .../kotlin/at/xirado/jdui/JDUIListener.kt | 11 ++--- .../handler/ComponentInteractionHandler.kt | 27 +++++------ .../interaction/ViewComponentInteraction.kt | 9 ++-- .../main/kotlin/at/xirado/jdui/utils/JDA.kt | 48 ++++++++++++++----- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/JDUIListener.kt b/core/src/main/kotlin/at/xirado/jdui/JDUIListener.kt index f7c0fc5..15e9ae1 100644 --- a/core/src/main/kotlin/at/xirado/jdui/JDUIListener.kt +++ b/core/src/main/kotlin/at/xirado/jdui/JDUIListener.kt @@ -38,13 +38,10 @@ class JDUIListener(internal val config: JDUIConfig) : EventListener { // private val modalInteractionHandler = ModalInteractionHandler(this) override fun onEvent(event: GenericEvent) { - coroutineScope.launch { - when (event) { - is StatusChangeEvent -> handleStatusChange(event) - is ShutdownEvent -> handleShutdown(event) - is GenericComponentInteractionCreateEvent -> componentInteractionHandler.handleComponentEvent(event) -// is ModalInteractionEvent -> modalInteractionHandler.onModalEvent(event) - } + when (event) { + is StatusChangeEvent -> handleStatusChange(event) + is ShutdownEvent -> handleShutdown(event) + is GenericComponentInteractionCreateEvent -> componentInteractionHandler.handleComponentEvent(event) } } diff --git a/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt b/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt index 793edb2..01ba006 100644 --- a/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt +++ b/core/src/main/kotlin/at/xirado/jdui/handler/ComponentInteractionHandler.kt @@ -2,27 +2,24 @@ package at.xirado.jdui.handler import at.xirado.jdui.JDUIListener import at.xirado.jdui.state.interaction.ViewComponentInteraction -import io.github.oshai.kotlinlogging.KotlinLogging -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent -import net.dv8tion.jda.api.events.interaction.component.EntitySelectInteractionEvent +import kotlinx.coroutines.launch +import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent -import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent -private val log = KotlinLogging.logger { } +private val componentsV2Flag = Message.MessageFlag.IS_COMPONENTS_V2.value.toLong() internal class ComponentInteractionHandler(private val jdui: JDUIListener) { - private val config = jdui.config + private val coroutineScope = jdui.coroutineScope - suspend fun handleComponentEvent(event: GenericComponentInteractionCreateEvent) { - val now = System.currentTimeMillis() + fun handleComponentEvent(event: GenericComponentInteractionCreateEvent) { + if ((event.message.flagsRaw and componentsV2Flag) == 0L) + return - val interaction = when (event) { - is ButtonInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) - is StringSelectInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) - is EntitySelectInteractionEvent -> ViewComponentInteraction.fromEvent(jdui, event, now) - else -> TODO("Unsupported") - } ?: return + val now = System.currentTimeMillis() - interaction.process() + coroutineScope.launch { + val interaction = ViewComponentInteraction.fromEvent(jdui, event, now) + interaction?.process() + } } } \ No newline at end of file diff --git a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt index 7fd2f41..865077a 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt @@ -6,7 +6,7 @@ import at.xirado.jdui.component.StatefulActionComponent import at.xirado.jdui.state.ViewState import at.xirado.jdui.state.getViewStateByDiscordCustomIds import at.xirado.jdui.utils.await -import at.xirado.jdui.utils.mergeCustomIds +import at.xirado.jdui.utils.tryMergeIds import at.xirado.jdui.view.ViewDSL import at.xirado.jdui.view.createFunctionViewState import at.xirado.jdui.view.definition.function.ViewDefinitionFunction @@ -18,6 +18,8 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData import net.dv8tion.jda.api.utils.messages.MessageEditData import kotlin.reflect.KFunction +internal val allowedCustomIdPrefixes = setOf("j1:", "j2:") + @ViewDSL class ViewComponentInteraction private constructor( internal val state: ViewState, @@ -37,7 +39,7 @@ class ViewComponentInteraction privat internal suspend fun process() = mutex.withLock { if (isInitialized) - throw IllegalStateException("Can only call processEvent() once!") + throw IllegalStateException("Can only call process() once!") val component = initialize() @@ -79,7 +81,8 @@ class ViewComponentInteraction privat event: E, receivedTimestamp: Long, ): ViewComponentInteraction? { - val id = event.message.components.mergeCustomIds() + val id = event.message.components.tryMergeIds(allowedCustomIdPrefixes) + ?: return null val state = getViewStateByDiscordCustomIds(jdui, id) ?: return null return ViewComponentInteraction(state, event, receivedTimestamp) diff --git a/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt b/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt index cb3c172..1bd9775 100644 --- a/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt +++ b/core/src/main/kotlin/at/xirado/jdui/utils/JDA.kt @@ -3,26 +3,50 @@ package at.xirado.jdui.utils import net.dv8tion.jda.api.components.ActionComponent import net.dv8tion.jda.api.components.Component import net.dv8tion.jda.api.components.actionrow.ActionRow -import net.dv8tion.jda.api.components.buttons.Button import net.dv8tion.jda.api.components.container.Container import net.dv8tion.jda.api.components.section.Section -fun Collection.mergeCustomIds(): String { +fun Collection.tryMergeIds(allowedPrefixes: Collection): String? { val sb = StringBuilder() - forEach { component -> + val result = walkUntil { component -> + if (component !is ActionComponent) + return@walkUntil true + + val id = component.customId ?: return@walkUntil true + + if (sb.isEmpty() && allowedPrefixes.none { id.startsWith(it) }) + return@walkUntil false + + sb.append(id) + true + } + + return if (!result || sb.isEmpty()) + null + else + sb.toString() +} + +internal fun Collection.walkUntil(block: (Component) -> Boolean): Boolean { + for (component in this) { + if (!block(component)) return false + when (component) { - is ActionRow -> sb.append(component.components.mergeCustomIds()) - is Container -> sb.append(component.components.mergeCustomIds()) - is ActionComponent -> component.customId?.let { sb.append(it) } + is ActionRow -> { + if (!component.components.walkUntil(block)) + return false + } + is Container -> { + if (!component.components.walkUntil(block)) + return false + } is Section -> { - if (component.accessory is Button) { - sb.append(component.accessory.asButton().customId) - } - component.contentComponents.mergeCustomIds() + if (!block(component.accessory)) return false + if (!component.contentComponents.walkUntil(block)) + return false } } } - - return sb.toString() + return true } \ No newline at end of file From 384a0201c63d5db04ea72027c3a35a6923a38b4e Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Tue, 1 Jul 2025 19:38:57 +0200 Subject: [PATCH 11/13] Add ViewMiddleware --- .../kotlin/at/xirado/jdui/state/ViewState.kt | 2 ++ .../interaction/ViewComponentInteraction.kt | 7 +++++ .../jdui/view/middleware/ViewMiddleware.kt | 17 ++++++++++++ .../jdui/view/middleware/WhitelistedUsers.kt | 26 +++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 core/src/main/kotlin/at/xirado/jdui/view/middleware/ViewMiddleware.kt create mode 100644 core/src/main/kotlin/at/xirado/jdui/view/middleware/WhitelistedUsers.kt diff --git a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt index 9f65a2b..ce25100 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/ViewState.kt @@ -12,6 +12,7 @@ import at.xirado.jdui.view.metadata.source.ClassViewSourceData import at.xirado.jdui.view.metadata.source.FunctionViewSourceData import at.xirado.jdui.view.metadata.source.ViewSourceCache import at.xirado.jdui.view.metadata.source.ViewSourceData +import at.xirado.jdui.view.middleware.ViewMiddleware import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex @@ -37,6 +38,7 @@ class ViewState internal constructor( internal var mutex = Mutex() internal val context = Context(listener.context) internal val messageContext = MessageContext(this) + internal val middleware: MutableList = mutableListOf() private var userState = metadata.metadata.sourceData?.let { if (supportUserState) UserStateCollection(metadata.metadata.userState) else null diff --git a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt index 865077a..5244671 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/interaction/ViewComponentInteraction.kt @@ -41,6 +41,13 @@ class ViewComponentInteraction privat if (isInitialized) throw IllegalStateException("Can only call process() once!") + val middlewares = state.middleware + + for (middleware in middlewares) { + if (!middleware.processEvent(event)) + return@withLock + } + val component = initialize() state.messageContext.provideInteractionHook(event.hook) diff --git a/core/src/main/kotlin/at/xirado/jdui/view/middleware/ViewMiddleware.kt b/core/src/main/kotlin/at/xirado/jdui/view/middleware/ViewMiddleware.kt new file mode 100644 index 0000000..870d931 --- /dev/null +++ b/core/src/main/kotlin/at/xirado/jdui/view/middleware/ViewMiddleware.kt @@ -0,0 +1,17 @@ +package at.xirado.jdui.view.middleware + +import at.xirado.jdui.view.View +import at.xirado.jdui.view.definition.ViewDefinition +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent + +interface ViewMiddleware { + suspend fun processEvent(event: GenericComponentInteractionCreateEvent): Boolean +} + +fun ViewDefinition.middleware(middleware: ViewMiddleware) { + state.middleware += middleware +} + +fun View.middleware(middleware: ViewMiddleware) { + withDefinition { it.state.middleware += middleware } +} \ No newline at end of file diff --git a/core/src/main/kotlin/at/xirado/jdui/view/middleware/WhitelistedUsers.kt b/core/src/main/kotlin/at/xirado/jdui/view/middleware/WhitelistedUsers.kt new file mode 100644 index 0000000..91f72e4 --- /dev/null +++ b/core/src/main/kotlin/at/xirado/jdui/view/middleware/WhitelistedUsers.kt @@ -0,0 +1,26 @@ +package at.xirado.jdui.view.middleware + +import net.dv8tion.jda.api.components.container.Container +import net.dv8tion.jda.api.components.textdisplay.TextDisplay +import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder + +private val NOT_WHITELISTED_MESSAGE = MessageCreateBuilder() + .useComponentsV2() + .addComponents( + Container.of( + TextDisplay.of("You cannot interact with this message!") + ).withAccentColor(0xFF0000) + ) + .build() + +class WhitelistedUsers(val userIds: Collection) : ViewMiddleware { + override suspend fun processEvent(event: GenericComponentInteractionCreateEvent): Boolean { + val isWhitelisted = event.user.idLong in userIds + + if (!isWhitelisted) + event.reply(NOT_WHITELISTED_MESSAGE).setEphemeral(true).queue() + + return isWhitelisted + } +} \ No newline at end of file From df40bb519f8c12d07c4ccf6fefc212e00147b4e2 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Tue, 1 Jul 2025 19:39:20 +0200 Subject: [PATCH 12/13] Make state default a function --- .../main/kotlin/at/xirado/jdui/state/UserStateCollection.kt | 2 +- .../src/main/kotlin/at/xirado/jdui/state/UserStateProperty.kt | 2 +- core/src/main/kotlin/at/xirado/jdui/state/ViewUserState.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/at/xirado/jdui/state/UserStateCollection.kt b/core/src/main/kotlin/at/xirado/jdui/state/UserStateCollection.kt index 9fbc23b..85aba4d 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/UserStateCollection.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/UserStateCollection.kt @@ -21,7 +21,7 @@ internal class UserStateCollection( return if (index > unpackedUserState.lastIndex) { - userState[property] = property.default + userState[property] = property.default() return } diff --git a/core/src/main/kotlin/at/xirado/jdui/state/UserStateProperty.kt b/core/src/main/kotlin/at/xirado/jdui/state/UserStateProperty.kt index fff3dd3..34e3c69 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/UserStateProperty.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/UserStateProperty.kt @@ -7,7 +7,7 @@ import kotlin.reflect.KProperty class UserStateProperty internal constructor( internal val index: Int, internal val property: KProperty, - internal val default: T, + internal val default: () -> T, internal val state: ViewState, ): ReadWriteProperty { internal val serializer = serializer(property.returnType) diff --git a/core/src/main/kotlin/at/xirado/jdui/state/ViewUserState.kt b/core/src/main/kotlin/at/xirado/jdui/state/ViewUserState.kt index 69ddfeb..9a41a80 100644 --- a/core/src/main/kotlin/at/xirado/jdui/state/ViewUserState.kt +++ b/core/src/main/kotlin/at/xirado/jdui/state/ViewUserState.kt @@ -5,7 +5,7 @@ import at.xirado.jdui.view.definition.ViewDefinition import kotlin.properties.PropertyDelegateProvider import kotlin.reflect.KProperty -fun ViewDefinition.state(default: T): PropertyDelegateProvider> { +fun ViewDefinition.state(default: () -> T): PropertyDelegateProvider> { val viewState = this.state val userState = this.userState @@ -20,6 +20,6 @@ fun ViewDefinition.state(default: T): PropertyDelegateProvider View.state(default: T): PropertyDelegateProvider> { +fun View.state(default: () -> T): PropertyDelegateProvider> { return withDefinition { it.state(default) } } \ No newline at end of file From 679953540987a9d9f1ba1ac20fb3508cbf939be7 Mon Sep 17 00:00:00 2001 From: Marcel Korzonek Date: Tue, 1 Jul 2025 20:23:59 +0200 Subject: [PATCH 13/13] Fix examples --- .../main/kotlin/at/xirado/jdui/example/view/CatView.kt | 2 +- .../kotlin/at/xirado/jdui/example/view/ComponentTest.kt | 4 ++-- .../kotlin/at/xirado/jdui/example/view/CounterView.kt | 6 +++--- .../at/xirado/jdui/example/view/MultipleMenusView.kt | 6 +++--- .../main/kotlin/at/xirado/jdui/example/view/StateTest.kt | 8 ++++---- .../at/xirado/jdui/example/view/UrbanDictionaryView.kt | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt index 4416ca0..e1f3ff7 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/CatView.kt @@ -26,7 +26,7 @@ private const val CAT_URL = "https://api.thecatapi.com/v1/images/search?limit=10 class CatView : View() { private val httpClient: OkHttpClient by context - private val cats: MutableList by state(mutableListOf()) + private val cats: MutableList by state { mutableListOf() } private var currentCatJob: Deferred? = null diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt index 2d0362a..d2fccc7 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/ComponentTest.kt @@ -20,8 +20,8 @@ private val emojis = mapOf( ) fun componentTest() = view { - var description: String? by state(null) - var emojiString: String by state("") + var description: String? by state { null } + var emojiString: String by state { "" } val file = { val bytes = "Hello World".byteInputStream() diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt index 673ee8a..f3e1351 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/CounterView.kt @@ -15,9 +15,9 @@ data class LastUpdate( ) fun counterView() = view { - var counter: Int by state(0) - var step: Int by state(1) - var lastUpdate: LastUpdate? by state(null) + var counter: Int by state { 0 } + var step: Int by state { 1 } + var lastUpdate: LastUpdate? by state { null } compose { +text( diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt index aefff09..9c2d77e 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/MultipleMenusView.kt @@ -15,9 +15,9 @@ import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.utils.TimeFormat class MultipleMenusView : View() { - private var isConfirmationView by state(false) - private var isLoading by state(false) - private var data: String? by state(null) + private var isConfirmationView by state { false } + private var isLoading by state { false } + private var data: String? by state { null } override suspend fun createView() = compose { +container(0x4287f5) { diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt index 6a11f43..d58b0f1 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/StateTest.kt @@ -9,10 +9,10 @@ import at.xirado.jdui.view.definition.function.view import net.dv8tion.jda.api.components.buttons.ButtonStyle.* fun stateTest() = view { - var someInt by state(0) - var someString by state("") - var someNullableInt: Int? by state(null) - var someNullableString: String? by state(null) + var someInt by state { 0 } + var someString by state { "" } + var someNullableInt: Int? by state { null } + var someNullableString: String? by state { null } compose { +container(0x00ff00) { diff --git a/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt b/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt index 82c78a0..7b6ea80 100644 --- a/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt +++ b/example/src/main/kotlin/at/xirado/jdui/example/view/UrbanDictionaryView.kt @@ -22,9 +22,9 @@ import java.time.Instant class UrbanDictionaryView : View() { private val httpClient: OkHttpClient by context - private val query: UrbanDictionaryQuery? by state(context.get()) - private var pageIndex: Int by state(0) - private var definitions: List by state(emptyList()) + private val query: UrbanDictionaryQuery? by state { context.get() } + private var pageIndex: Int by state { 0 } + private var definitions: List by state { emptyList() } init { require(query != null) { "UrbanDictionaryQuery not present in context!" }