From d86da76709dcd52e0019f21f273e2063cf8aeea6 Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:41:12 +0100 Subject: [PATCH 1/7] feat: more selector types --- .../kord/extensions/components/_Functions.kt | 212 ++++++++++++++++-- .../extensions/components/menus/SelectMenu.kt | 69 ------ .../components/menus/SelectMenuContext.kt | 6 +- .../menus/channel/ChannelSelectMenu.kt | 41 ++++ .../menus/channel/ChannelSelectMenuContext.kt | 29 +++ .../channel/EphemeralChannelSelectMenu.kt | 128 +++++++++++ .../EphemeralChannelSelectMenuContext.kt | 21 ++ .../menus/channel/PublicChannelSelectMenu.kt | 129 +++++++++++ .../channel/PublicChannelSelectMenuContext.kt | 21 ++ .../EphemeralRoleSelectMenu.kt} | 17 +- .../EphemeralRoleSelectMenuContext.kt} | 10 +- .../PublicRoleSelectMenu.kt} | 17 +- .../menus/role/PublicRoleSelectMenuContext.kt | 21 ++ .../components/menus/role/RoleSelectMenu.kt | 27 +++ .../menus/role/RoleSelectMenuContext.kt | 35 +++ .../menus/string/EphemeralStringSelectMenu.kt | 128 +++++++++++ .../EphemeralStringSelectMenuContext.kt | 21 ++ .../menus/string/PublicStringSelectMenu.kt | 129 +++++++++++ .../PublicStringSelectMenuContext.kt} | 10 +- .../menus/string/StringSelectMenu.kt | 85 +++++++ .../menus/string/StringSelectMenuContext.kt | 22 ++ .../menus/user/EphemeralUserSelectMenu.kt | 128 +++++++++++ .../user/EphemeralUserSelectMenuContext.kt | 21 ++ .../menus/user/PublicUserSelectMenu.kt | 129 +++++++++++ .../menus/user/PublicUserSelectMenuContext.kt | 21 ++ .../components/menus/user/UserSelectMenu.kt | 27 +++ .../menus/user/UserSelectMenuContext.kt | 29 +++ .../kord/extensions/testbot/TestBot.kt | 1 + .../extensions/SelectorTestExtension.kt | 92 ++++++++ 29 files changed, 1503 insertions(+), 123 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/{EphemeralSelectMenu.kt => role/EphemeralRoleSelectMenu.kt} (89%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/{EphemeralSelectMenuContext.kt => role/EphemeralRoleSelectMenuContext.kt} (73%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/{PublicSelectMenu.kt => role/PublicRoleSelectMenu.kt} (90%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/{PublicSelectMenuContext.kt => string/PublicStringSelectMenuContext.kt} (73%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt create mode 100644 test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt index 98a094dd09..c3dbc478a7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt @@ -11,8 +11,14 @@ import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteraction import com.kotlindiscord.kord.extensions.components.buttons.LinkInteractionButton import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButton import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu -import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.channel.EphemeralChannelSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.channel.PublicChannelSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.role.EphemeralRoleSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.role.PublicRoleSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.string.EphemeralStringSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.string.PublicStringSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.user.EphemeralUserSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.user.PublicUserSelectMenu import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.modify.MessageModifyBuilder import kotlin.time.Duration @@ -97,12 +103,12 @@ public suspend fun ComponentContainer.publicButton( return component } -/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/ -public suspend fun ComponentContainer.ephemeralSelectMenu( +/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralStringSelectMenu( row: Int? = null, - builder: suspend EphemeralSelectMenu.() -> Unit -): EphemeralSelectMenu { - val component = EphemeralSelectMenu(timeoutTask) + builder: suspend EphemeralStringSelectMenu.() -> Unit +): EphemeralStringSelectMenu { + val component = EphemeralStringSelectMenu(timeoutTask) builder(component) add(component, row) @@ -110,13 +116,13 @@ public suspend fun ComponentContainer.ephemeralSelectMenu( return component } -/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/ -public suspend fun ComponentContainer.ephemeralSelectMenu( +/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralStringSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralSelectMenu.() -> Unit -): EphemeralSelectMenu { - val component = EphemeralSelectMenu(timeoutTask, modal) + builder: suspend EphemeralStringSelectMenu.() -> Unit +): EphemeralStringSelectMenu { + val component = EphemeralStringSelectMenu(timeoutTask, modal) builder(component) add(component, row) @@ -124,12 +130,12 @@ public suspend fun ComponentContainer.ephemeralSelectMenu( return component } -/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/ -public suspend fun ComponentContainer.publicSelectMenu( +/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicStringSelectMenu( row: Int? = null, - builder: suspend PublicSelectMenu.() -> Unit -): PublicSelectMenu { - val component = PublicSelectMenu(timeoutTask) + builder: suspend PublicStringSelectMenu.() -> Unit +): PublicStringSelectMenu { + val component = PublicStringSelectMenu(timeoutTask) builder(component) add(component, row) @@ -137,13 +143,175 @@ public suspend fun ComponentContainer.publicSelectMenu( return component } -/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/ -public suspend fun ComponentContainer.publicSelectMenu( +/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicStringSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicSelectMenu.() -> Unit -): PublicSelectMenu { - val component = PublicSelectMenu(timeoutTask, modal) + builder: suspend PublicStringSelectMenu.() -> Unit +): PublicStringSelectMenu { + val component = PublicStringSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralUserSelectMenu( + row: Int? = null, + builder: suspend EphemeralUserSelectMenu.() -> Unit +): EphemeralUserSelectMenu { + val component = EphemeralUserSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralUserSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend EphemeralUserSelectMenu.() -> Unit +): EphemeralUserSelectMenu { + val component = EphemeralUserSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicUserSelectMenu( + row: Int? = null, + builder: suspend PublicUserSelectMenu.() -> Unit +): PublicUserSelectMenu { + val component = PublicUserSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicUserSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend PublicUserSelectMenu.() -> Unit +): PublicUserSelectMenu { + val component = PublicUserSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralRoleSelectMenu( + row: Int? = null, + builder: suspend EphemeralRoleSelectMenu.() -> Unit +): EphemeralRoleSelectMenu { + val component = EphemeralRoleSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralRoleSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend EphemeralRoleSelectMenu.() -> Unit +): EphemeralRoleSelectMenu { + val component = EphemeralRoleSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicRoleSelectMenu( + row: Int? = null, + builder: suspend PublicRoleSelectMenu.() -> Unit +): PublicRoleSelectMenu { + val component = PublicRoleSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicRoleSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend PublicRoleSelectMenu.() -> Unit +): PublicRoleSelectMenu { + val component = PublicRoleSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralChannelSelectMenu( + row: Int? = null, + builder: suspend EphemeralChannelSelectMenu.() -> Unit +): EphemeralChannelSelectMenu { + val component = EphemeralChannelSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.ephemeralChannelSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend EphemeralChannelSelectMenu.() -> Unit +): EphemeralChannelSelectMenu { + val component = EphemeralChannelSelectMenu(timeoutTask, modal) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicChannelSelectMenu( + row: Int? = null, + builder: suspend PublicChannelSelectMenu.() -> Unit +): PublicChannelSelectMenu { + val component = PublicChannelSelectMenu(timeoutTask) + + builder(component) + add(component, row) + + return component +} + +/** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ +public suspend fun ComponentContainer.publicChannelSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend PublicChannelSelectMenu.() -> Unit +): PublicChannelSelectMenu { + val component = PublicChannelSelectMenu(timeoutTask, modal) builder(component) add(component, row) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index 3ed9a6cee0..63c35484dd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -15,8 +15,6 @@ import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.entity.channel.DmChannel import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.component.SelectOptionBuilder import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging @@ -42,9 +40,6 @@ public abstract class SelectMenu( ) : ComponentWithAction(timeoutTask) { internal val logger: KLogger = KotlinLogging.logger {} - /** List of options for the user to choose from. **/ - public val options: MutableList = mutableListOf() - /** The minimum number of choices that the user must make. **/ public var minimumChoices: Int = 1 @@ -70,70 +65,6 @@ public abstract class SelectMenu( disabled = null // Don't ask me why this is } - /** Add an option to this select menu. **/ - @Suppress("UnnecessaryParentheses") // Disagrees with IDEA, amusingly. - public open suspend fun option( - label: String, - value: String, - - // TODO: Check this is fixed in later versions of the compiler - // This is nullable like this due to a compiler bug: https://youtrack.jetbrains.com/issue/KT-51820 - body: (suspend SelectOptionBuilder.() -> Unit)? = null, - ) { - val builder = SelectOptionBuilder(label, value) - - if (body != null) { - body(builder) - } - - if ((builder.description?.length ?: 0) > DESCRIPTION_MAX) { - error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.") - } - - if (builder.label.length > LABEL_MAX) { - error("Option labels must not be longer than $LABEL_MAX characters.") - } - - if (builder.value.length > VALUE_MAX) { - error("Option values must not be longer than $VALUE_MAX characters.") - } - - options.add(builder) - } - - public override fun apply(builder: ActionRowBuilder) { - if (maximumChoices == null || maximumChoices!! > options.size) { - maximumChoices = options.size - } - - builder.stringSelect(id) { - this.allowedValues = minimumChoices..maximumChoices!! - - @Suppress("DEPRECATION") // Kord suppresses this in their own class - this.options.addAll(this@SelectMenu.options) - this.placeholder = this@SelectMenu.placeholder - - this.disabled = this@SelectMenu.disabled - } - } - - @Suppress("UnnecessaryParentheses") // Disagrees with IDEA, amusingly. - override fun validate() { - super.validate() - - if (this.options.isEmpty()) { - error("Menu components must have at least one option.") - } - - if (this.options.size > OPTIONS_MAX) { - error("Menu components must not have more than $OPTIONS_MAX options.") - } - - if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) { - error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") - } - } - /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ public open suspend fun firstSentryBreadcrumb(context: C, button: SelectMenu<*, *>) { if (sentry.enabled) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt index 7b65dc24bb..efea4fa6a5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt @@ -12,11 +12,9 @@ import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a select (dropdown) menu component. **/ +@Suppress("UnnecessaryAbstractClass") public abstract class SelectMenuContext( component: Component, event: SelectMenuInteractionCreateEvent, cache: MutableStringKeyedMap -) : ComponentContext(component, event, cache) { - /** Menu options that were selected by the user before de-focusing the menu. **/ - public val selected: List by lazy { event.interaction.values } -} +) : ComponentContext(component, event, cache) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt new file mode 100644 index 0000000000..49bd938f4c --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt @@ -0,0 +1,41 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.* +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.entity.ChannelType +import dev.kord.rest.builder.component.ActionRowBuilder + +/** Abstract class representing a channel select (dropdown) menu component. **/ +public abstract class ChannelSelectMenu(timeoutTask: Task?) : + SelectMenu(timeoutTask) { + + /** The channel types allowed to be selected in the dropdown. **/ + public var channelTypes: MutableList = mutableListOf() + + /** Add an allowed channel type to the selector. **/ + public fun channelType(vararg type: ChannelType) { + channelTypes.addAll(type) + } + + public override fun apply(builder: ActionRowBuilder) { + if (maximumChoices == null) maximumChoices = OPTIONS_MAX + + builder.channelSelect(id) { + this.channelTypes = if (this@ChannelSelectMenu.channelTypes.isEmpty()) { + null + } else { + this@ChannelSelectMenu.channelTypes + } + this.allowedValues = minimumChoices..maximumChoices!! + this.placeholder = this@ChannelSelectMenu.placeholder + this.disabled = this@ChannelSelectMenu.disabled + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt new file mode 100644 index 0000000000..b451d5a1b6 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt @@ -0,0 +1,29 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.channel.ChannelBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Abstract class representing the execution context of a channel select (dropdown) menu component. **/ +public abstract class ChannelSelectMenuContext( + component: Component, + event: SelectMenuInteractionCreateEvent, + cache: MutableStringKeyedMap, +) : SelectMenuContext(component, event, cache) { + /** Menu options that were selected by the user before de-focusing the menu. **/ + @OptIn(KordUnsafe::class, KordExperimental::class) + public val selected: List by lazy { + event.interaction.values.map { event.kord.unsafe.channel(Snowflake(it)) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt new file mode 100644 index 0000000000..351c0f4340 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt @@ -0,0 +1,128 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialEphemeralSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing an ephemeral-only channel select (dropdown) menu. **/ +public open class EphemeralChannelSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : ChannelSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferEphemeralResponseUnsafe() + } else { + it?.deferEphemeralMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferEphemeralResponseUnsafe() + } else { + event.interaction.deferEphemeralMessageUpdate() + } + } + + val context = EphemeralChannelSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: EphemeralChannelSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt new file mode 100644 index 0000000000..c4ec59f2ae --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for an ephemeral-only channel select (dropdown) menu. **/ +public class EphemeralChannelSelectMenuContext( + override val component: EphemeralChannelSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : ChannelSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt new file mode 100644 index 0000000000..1eb617009a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt @@ -0,0 +1,129 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialPublicSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing a public-only channel select (dropdown) menu. **/ +public open class PublicChannelSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : ChannelSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferPublicResponseUnsafe() + } else { + it?.deferPublicMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferPublicResponseUnsafe() + } else { + event.interaction.deferPublicMessageUpdate() + } + } + + val context = PublicChannelSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: PublicChannelSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt new file mode 100644 index 0000000000..124059be45 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.channel + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for a public-only channel select (dropdown) menu. **/ +public class PublicChannelSelectMenuContext( + override val component: PublicChannelSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : ChannelSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt similarity index 89% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt index d572430421..386bc4cd2b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt @@ -4,10 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -@file:Suppress("TooGenericExceptionCaught") -@file:OptIn(KordUnsafe::class) - -package com.kotlindiscord.kord.extensions.components.menus +package com.kotlindiscord.kord.extensions.components.menus.role import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm @@ -25,11 +22,11 @@ import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? -/** Class representing an ephemeral-only select (dropdown) menu. **/ -public open class EphemeralSelectMenu( +/** Class representing an ephemeral-only role select (dropdown) menu. **/ +public open class EphemeralRoleSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : SelectMenu, M>(timeoutTask) { +) : RoleSelectMenu, M>(timeoutTask) { /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null @@ -46,6 +43,8 @@ public open class EphemeralSelectMenu( } } + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { val cache: MutableStringKeyedMap = mutableMapOf() @@ -96,7 +95,7 @@ public open class EphemeralSelectMenu( } } - val context = EphemeralSelectMenuContext(this, event, response, cache) + val context = EphemeralRoleSelectMenuContext(this, event, response, cache) context.populate() @@ -120,7 +119,7 @@ public open class EphemeralSelectMenu( } override suspend fun respondText( - context: EphemeralSelectMenuContext, + context: EphemeralRoleSelectMenuContext, message: String, failureType: FailureReason<*> ) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt similarity index 73% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt index 00e46f02dd..c87e63d813 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -package com.kotlindiscord.kord.extensions.components.menus +package com.kotlindiscord.kord.extensions.components.menus.role import com.kotlindiscord.kord.extensions.components.forms.ModalForm import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext @@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -/** Class representing the execution context for an ephemeral-only select (dropdown) menu. **/ -public class EphemeralSelectMenuContext( - override val component: EphemeralSelectMenu, +/** Class representing the execution context for an ephemeral-only role select (dropdown) menu. **/ +public class EphemeralRoleSelectMenuContext( + override val component: EphemeralRoleSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: EphemeralMessageInteractionResponseBehavior, cache: MutableStringKeyedMap -) : SelectMenuContext(component, event, cache), EphemeralInteractionContext +) : RoleSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt similarity index 90% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt index 4ef24a0333..7925f19930 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt @@ -4,10 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -@file:Suppress("TooGenericExceptionCaught") -@file:OptIn(KordUnsafe::class) - -package com.kotlindiscord.kord.extensions.components.menus +package com.kotlindiscord.kord.extensions.components.menus.role import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm @@ -26,11 +23,11 @@ import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSelectMenuResponseBuilder = (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? -/** Class representing a public-only select (dropdown) menu. **/ -public open class PublicSelectMenu( +/** Class representing a public-only role select (dropdown) menu. **/ +public open class PublicRoleSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : SelectMenu, M>(timeoutTask) { +) : RoleSelectMenu, M>(timeoutTask) { /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null @@ -47,6 +44,8 @@ public open class PublicSelectMenu( } } + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { val cache: MutableStringKeyedMap = mutableMapOf() @@ -97,7 +96,7 @@ public open class PublicSelectMenu( } } - val context = PublicSelectMenuContext(this, event, response, cache) + val context = PublicRoleSelectMenuContext(this, event, response, cache) context.populate() @@ -121,7 +120,7 @@ public open class PublicSelectMenu( } override suspend fun respondText( - context: PublicSelectMenuContext, + context: PublicRoleSelectMenuContext, message: String, failureType: FailureReason<*> ) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt new file mode 100644 index 0000000000..0f8d1aedf2 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.role + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for a public-only role select (dropdown) menu. **/ +public class PublicRoleSelectMenuContext( + override val component: PublicRoleSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : RoleSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt new file mode 100644 index 0000000000..89c8ba5241 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt @@ -0,0 +1,27 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.role + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.* +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.rest.builder.component.ActionRowBuilder + +/** Abstract class representing a role select (dropdown) menu component. **/ +public abstract class RoleSelectMenu(timeoutTask: Task?) : + SelectMenu(timeoutTask) { + + public override fun apply(builder: ActionRowBuilder) { + if (maximumChoices == null) maximumChoices = OPTIONS_MAX + + builder.roleSelect(id) { + this.allowedValues = minimumChoices..maximumChoices!! + this.placeholder = this@RoleSelectMenu.placeholder + this.disabled = this@RoleSelectMenu.disabled + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt new file mode 100644 index 0000000000..2bd0a073d0 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt @@ -0,0 +1,35 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.role + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.RoleBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Abstract class representing the execution context of a role select (dropdown) menu component. **/ +public abstract class RoleSelectMenuContext( + component: Component, + event: SelectMenuInteractionCreateEvent, + cache: MutableStringKeyedMap, +) : SelectMenuContext(component, event, cache) { + /** Menu options that were selected by the user before de-focusing the menu. **/ + @OptIn(KordUnsafe::class, KordExperimental::class) + public val selected: List by lazy { + if (event.interaction.data.guildId.value == null) { + error("A role select menu cannot be used outside guilds.") + } else { + event.interaction.values.map { + event.kord.unsafe.role(event.interaction.data.guildId.value!!, Snowflake(it)) + } + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt new file mode 100644 index 0000000000..3d39a7f478 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt @@ -0,0 +1,128 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.string + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialEphemeralSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing an ephemeral-only string select (dropdown) menu. **/ +public open class EphemeralStringSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : StringSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferEphemeralResponseUnsafe() + } else { + it?.deferEphemeralMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferEphemeralResponseUnsafe() + } else { + event.interaction.deferEphemeralMessageUpdate() + } + } + + val context = EphemeralStringSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: EphemeralStringSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt new file mode 100644 index 0000000000..605942a33e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.string + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for an ephemeral-only string select (dropdown) menu. **/ +public class EphemeralStringSelectMenuContext( + override val component: EphemeralStringSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : StringSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt new file mode 100644 index 0000000000..f432fe39f3 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt @@ -0,0 +1,129 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.string + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialPublicSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing a public-only string select (dropdown) menu. **/ +public open class PublicStringSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : StringSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferPublicResponseUnsafe() + } else { + it?.deferPublicMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferPublicResponseUnsafe() + } else { + event.interaction.deferPublicMessageUpdate() + } + } + + val context = PublicStringSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: PublicStringSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt similarity index 73% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt index a3d3436aff..5c19eeee6f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -package com.kotlindiscord.kord.extensions.components.menus +package com.kotlindiscord.kord.extensions.components.menus.string import com.kotlindiscord.kord.extensions.components.forms.ModalForm import com.kotlindiscord.kord.extensions.types.PublicInteractionContext @@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -/** Class representing the execution context for a public-only select (dropdown) menu. **/ -public class PublicSelectMenuContext( - override val component: PublicSelectMenu, +/** Class representing the execution context for a public-only string select (dropdown) menu. **/ +public class PublicStringSelectMenuContext( + override val component: PublicStringSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: PublicMessageInteractionResponseBehavior, cache: MutableStringKeyedMap -) : SelectMenuContext(component, event, cache), PublicInteractionContext +) : StringSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt new file mode 100644 index 0000000000..4fddd66ae4 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt @@ -0,0 +1,85 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.string + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.* +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.component.SelectOptionBuilder + +/** Abstract class representing a string select (dropdown) menu component. **/ +public abstract class StringSelectMenu(timeoutTask: Task?) : + SelectMenu(timeoutTask) { + + /** List of options for the user to choose from. **/ + public val options: MutableList = mutableListOf() + + /** Add an option to this select menu. **/ + @Suppress("UnnecessaryParentheses") + public open suspend fun option( + label: String, + value: String, + + // TODO: Check this is fixed in later versions of the compiler + // This is nullable like this due to a compiler bug: https://youtrack.jetbrains.com/issue/KT-51820 + body: (suspend SelectOptionBuilder.() -> Unit)? = null, + ) { + val builder = SelectOptionBuilder(label, value) + + if (body != null) { + body(builder) + } + + if ((builder.description?.length ?: 0) > DESCRIPTION_MAX) { + error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.") + } + + if (builder.label.length > LABEL_MAX) { + error("Option labels must not be longer than $LABEL_MAX characters.") + } + + if (builder.value.length > VALUE_MAX) { + error("Option values must not be longer than $VALUE_MAX characters.") + } + + options.add(builder) + } + + public override fun apply(builder: ActionRowBuilder) { + if (maximumChoices == null || maximumChoices!! > options.size) { + maximumChoices = options.size + } + + builder.stringSelect(id) { + this.allowedValues = minimumChoices..maximumChoices!! + + @Suppress("DEPRECATION") // Kord suppresses this in their own class + this.options.addAll(this@StringSelectMenu.options) + this.placeholder = this@StringSelectMenu.placeholder + + this.disabled = this@StringSelectMenu.disabled + } + } + + @Suppress("UnnecessaryParentheses") + override fun validate() { + super.validate() + + if (this.options.isEmpty()) { + error("Menu components must have at least one option.") + } + + if (this.options.size > OPTIONS_MAX) { + error("Menu components must not have more than $OPTIONS_MAX options.") + } + + if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) { + error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt new file mode 100644 index 0000000000..1b868a11af --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt @@ -0,0 +1,22 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.string + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Abstract class representing the execution context of a string select (dropdown) menu component. **/ +public abstract class StringSelectMenuContext( + component: Component, + event: SelectMenuInteractionCreateEvent, + cache: MutableStringKeyedMap, +) : SelectMenuContext(component, event, cache) { + /** Menu options that were selected by the user before de-focusing the menu. **/ + public val selected: List by lazy { event.interaction.values } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt new file mode 100644 index 0000000000..dbed401bb4 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt @@ -0,0 +1,128 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialEphemeralSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing an ephemeral-only user select (dropdown) menu. **/ +public open class EphemeralUserSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : UserSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferEphemeralResponseUnsafe() + } else { + it?.deferEphemeralMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferEphemeralResponseUnsafe() + } else { + event.interaction.deferEphemeralMessageUpdate() + } + } + + val context = EphemeralUserSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: EphemeralUserSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt new file mode 100644 index 0000000000..ed16f1afbc --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for an ephemeral-only user select (dropdown) menu. **/ +public class EphemeralUserSelectMenuContext( + override val component: EphemeralUserSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : UserSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt new file mode 100644 index 0000000000..4ec1aadd52 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt @@ -0,0 +1,129 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder + +public typealias InitialPublicSelectMenuResponseBuilder = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing a public-only user select (dropdown) menu. **/ +public open class PublicUserSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : UserSelectMenu, M>(timeoutTask) { + /** @suppress Initial response builder. **/ + public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferPublicResponseUnsafe() + } else { + it?.deferPublicMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferPublicResponseUnsafe() + } else { + event.interaction.deferPublicMessageUpdate() + } + } + + val context = PublicUserSelectMenuContext(this, event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: PublicUserSelectMenuContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt new file mode 100644 index 0000000000..8c7f609c1e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class representing the execution context for a public-only user select (dropdown) menu. **/ +public class PublicUserSelectMenuContext( + override val component: PublicUserSelectMenu, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap +) : UserSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt new file mode 100644 index 0000000000..34978e10a7 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt @@ -0,0 +1,27 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.* +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.rest.builder.component.ActionRowBuilder + +/** Abstract class representing a user select (dropdown) menu component. **/ +public abstract class UserSelectMenu(timeoutTask: Task?) : + SelectMenu(timeoutTask) { + + public override fun apply(builder: ActionRowBuilder) { + if (maximumChoices == null) maximumChoices = OPTIONS_MAX + + builder.userSelect(id) { + this.allowedValues = minimumChoices..maximumChoices!! + this.placeholder = this@UserSelectMenu.placeholder + this.disabled = this@UserSelectMenu.disabled + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt new file mode 100644 index 0000000000..95ad80895f --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt @@ -0,0 +1,29 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus.user + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Abstract class representing the execution context of a user select (dropdown) menu component. **/ +public abstract class UserSelectMenuContext( + component: Component, + event: SelectMenuInteractionCreateEvent, + cache: MutableStringKeyedMap, +) : SelectMenuContext(component, event, cache) { + /** Menu options that were selected by the user before de-focusing the menu. **/ + @OptIn(KordUnsafe::class, KordExperimental::class) + public val selected: List by lazy { + event.interaction.values.map { event.kord.unsafe.user(Snowflake(it)) } + } +} diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt index b3d789cd3d..01179cfcf7 100644 --- a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/TestBot.kt @@ -77,6 +77,7 @@ public suspend fun main() { add(::ModalTestExtension) add(::PaginatorTestExtension) add(::PKTestExtension) + add(::SelectorTestExtension) } plugins { diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt new file mode 100644 index 0000000000..3cd67aa6a4 --- /dev/null +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt @@ -0,0 +1,92 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.testbot.extensions + +import com.kotlindiscord.kord.extensions.components.* +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand +import com.kotlindiscord.kord.extensions.types.respond +import dev.kord.common.entity.ChannelType +import dev.kord.core.behavior.channel.asChannelOf +import dev.kord.core.entity.channel.TextChannel +import kotlinx.coroutines.runBlocking + +public class SelectorTestExtension : Extension() { + override val name: String = "Select Menus Test" + + override suspend fun setup() { + publicSlashCommand { + name = "selector" + description = "Test selectors." + + action { + respond { + components { + publicStringSelectMenu { + option("hi", "1") + option("hi hi", "2") + maximumChoices = null + + action { + respond { content = selected.joinToString("\n") } + } + } + + publicUserSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asUser().username } + } + } + } + } + + publicRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asRole().name } + } + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannel().id.value.toString() } + } + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannelOf().name } + } + } + } + } + } + } + } + } + } +} From 886557aae18eed9d5340e774a63bf51ddee6fcbb Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:30:20 +0100 Subject: [PATCH 2/7] Cleanup select menus and minimize duplicate code in select menus --- .../components/menus/EphemeralSelectMenu.kt | 134 ++++++++++++++++ .../components/menus/PublicSelectMenu.kt | 135 ++++++++++++++++ .../extensions/components/menus/SelectMenu.kt | 9 ++ .../components/menus/SelectMenuContext.kt | 5 +- .../menus/channel/ChannelSelectMenu.kt | 34 ++-- .../menus/channel/ChannelSelectMenuContext.kt | 4 +- .../channel/EphemeralChannelSelectMenu.kt | 122 ++------------ .../EphemeralChannelSelectMenuContext.kt | 2 +- .../menus/channel/PublicChannelSelectMenu.kt | 123 ++------------ .../channel/PublicChannelSelectMenuContext.kt | 2 +- .../menus/role/EphemeralRoleSelectMenu.kt | 119 ++------------ .../role/EphemeralRoleSelectMenuContext.kt | 2 +- .../menus/role/PublicRoleSelectMenu.kt | 120 ++------------ .../menus/role/PublicRoleSelectMenuContext.kt | 2 +- .../components/menus/role/RoleSelectMenu.kt | 29 ++-- .../menus/role/RoleSelectMenuContext.kt | 4 +- .../menus/string/EphemeralStringSelectMenu.kt | 122 ++------------ .../EphemeralStringSelectMenuContext.kt | 2 +- .../menus/string/PublicStringSelectMenu.kt | 123 ++------------ .../string/PublicStringSelectMenuContext.kt | 2 +- .../menus/string/StringSelectMenu.kt | 43 ++--- .../menus/string/StringSelectMenuContext.kt | 4 +- .../menus/user/EphemeralUserSelectMenu.kt | 119 ++------------ .../user/EphemeralUserSelectMenuContext.kt | 2 +- .../menus/user/PublicUserSelectMenu.kt | 120 ++------------ .../menus/user/PublicUserSelectMenuContext.kt | 2 +- .../components/menus/user/UserSelectMenu.kt | 29 ++-- .../menus/user/UserSelectMenuContext.kt | 4 +- .../extensions/SelectorTestExtension.kt | 151 +++++++++++++----- 29 files changed, 575 insertions(+), 994 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt new file mode 100644 index 0000000000..3bbb37514f --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -0,0 +1,134 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.channel.InitialEphemeralSelectMenuResponseBuilder +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Interface for ephemeral select menus. **/ +public abstract class EphemeralSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, + /** Builder for the initial response, omit to ack instead. **/ + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null, +) : SelectMenu(timeoutTask) where C : SelectMenuContext, C : EphemeralInteractionContext { + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + /** Function to create the context of the select menu. **/ + public abstract fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): C + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferEphemeralResponseUnsafe() + } else { + it?.deferEphemeralMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferEphemeralResponseUnsafe() + } else { + event.interaction.deferEphemeralMessageUpdate() + } + } + + val context = createContext(event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: C, + message: String, + failureType: FailureReason<*>, + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt new file mode 100644 index 0000000000..5fa1b61559 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -0,0 +1,135 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.channel.InitialEphemeralSelectMenuResponseBuilder +import com.kotlindiscord.kord.extensions.types.FailureReason +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.common.annotation.KordUnsafe +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +/** Class for public select menus. **/ +public abstract class PublicSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, + /** The initial response builder, omit to ack instead. **/ + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null, +) : SelectMenu(timeoutTask) where C : SelectMenuContext, C : PublicInteractionContext { + + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + + override fun validate() { + super.validate() + + if (modal != null && initialResponseBuilder != null) { + error("You may not provide a modal builder and an initial response - pick one, not both.") + } + } + + /** Function to create the context for the select menu. **/ + public abstract fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): C + + @OptIn(KordUnsafe::class) + @Suppress("TooGenericExceptionCaught") + override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { + val cache: MutableStringKeyedMap = mutableMapOf() + + super.call(event) + + try { + if (!runChecks(event, cache)) { + return@withLock + } + } catch (e: DiscordRelayedException) { + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } + + return@withLock + } + + val modalObj = modal?.invoke() + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else if (modalObj != null) { + componentRegistry.register(modalObj) + + val locale = event.getLocale() + + event.interaction.modal( + modalObj.translateTitle(locale, bundle), + modalObj.id + ) { + modalObj.applyToBuilder(this, event.getLocale(), bundle) + } + + modalObj.awaitCompletion { + componentRegistry.unregisterModal(modalObj) + + if (!deferredAck) { + it?.deferPublicResponseUnsafe() + } else { + it?.deferPublicMessageUpdate() + } + } ?: return@withLock + } else { + if (!deferredAck) { + event.interaction.deferPublicResponseUnsafe() + } else { + event.interaction.deferPublicMessageUpdate() + } + } + + val context = createContext(event, response, cache) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { + body(context, modalObj) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.RelayedFailure(e)) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText( + context: C, + message: String, + failureType: FailureReason<*>, + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index 63c35484dd..626ea50876 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -122,6 +122,15 @@ public abstract class SelectMenu( } } + @Suppress("UnnecessaryParentheses") + override fun validate() { + super.validate() + + if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) { + error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") + } + } + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt index efea4fa6a5..1cca94e7d3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt @@ -6,7 +6,6 @@ package com.kotlindiscord.kord.extensions.components.menus -import com.kotlindiscord.kord.extensions.components.Component import com.kotlindiscord.kord.extensions.components.ComponentContext import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent @@ -14,7 +13,7 @@ import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a select (dropdown) menu component. **/ @Suppress("UnnecessaryAbstractClass") public abstract class SelectMenuContext( - component: Component, + component: SelectMenu<*, *>, event: SelectMenuInteractionCreateEvent, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : ComponentContext(component, event, cache) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt index 49bd938f4c..ded9a0abd3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt @@ -1,41 +1,33 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - package com.kotlindiscord.kord.extensions.components.menus.channel -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.menus.* -import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import dev.kord.common.entity.ChannelType import dev.kord.rest.builder.component.ActionRowBuilder -/** Abstract class representing a channel select (dropdown) menu component. **/ -public abstract class ChannelSelectMenu(timeoutTask: Task?) : - SelectMenu(timeoutTask) { - - /** The channel types allowed to be selected in the dropdown. **/ - public var channelTypes: MutableList = mutableListOf() +/** Interface for channel select menus. **/ +public interface ChannelSelectMenu { + /** The types allowed in the select menu. **/ + public var channelTypes: MutableList /** Add an allowed channel type to the selector. **/ public fun channelType(vararg type: ChannelType) { channelTypes.addAll(type) } - public override fun apply(builder: ActionRowBuilder) { - if (maximumChoices == null) maximumChoices = OPTIONS_MAX + /** Apply the channel select menu to an action row builder. **/ + public fun applyChannelSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) { + if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX - builder.channelSelect(id) { + builder.channelSelect(selectMenu.id) { this.channelTypes = if (this@ChannelSelectMenu.channelTypes.isEmpty()) { null } else { this@ChannelSelectMenu.channelTypes } - this.allowedValues = minimumChoices..maximumChoices!! - this.placeholder = this@ChannelSelectMenu.placeholder - this.disabled = this@ChannelSelectMenu.disabled + this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! + this.placeholder = selectMenu.placeholder + this.disabled = selectMenu.disabled } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt index b451d5a1b6..acadd11d84 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenuContext.kt @@ -6,7 +6,7 @@ package com.kotlindiscord.kord.extensions.components.menus.channel -import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.common.annotation.KordExperimental @@ -17,7 +17,7 @@ import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a channel select (dropdown) menu component. **/ public abstract class ChannelSelectMenuContext( - component: Component, + component: SelectMenu<*, *>, event: SelectMenuInteractionCreateEvent, cache: MutableStringKeyedMap, ) : SelectMenuContext(component, event, cache) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt index 351c0f4340..8209df30fc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt @@ -6,17 +6,14 @@ package com.kotlindiscord.kord.extensions.components.menus.channel -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.common.entity.ChannelType +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = @@ -26,103 +23,16 @@ public typealias InitialEphemeralSelectMenuResponseBuilder = public open class EphemeralChannelSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : ChannelSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondEphemeral { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferEphemeralResponseUnsafe() - } else { - it?.deferEphemeralMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferEphemeralResponseUnsafe() - } else { - event.interaction.deferEphemeralMessageUpdate() - } - } - - val context = EphemeralChannelSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: EphemeralChannelSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : EphemeralSelectMenu, M>(timeoutTask), ChannelSelectMenu { + override var channelTypes: MutableList = mutableListOf() + + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): EphemeralChannelSelectMenuContext = EphemeralChannelSelectMenuContext( + this, event, interactionResponse, cache + ) + + public override fun apply(builder: ActionRowBuilder): Unit = applyChannelSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt index c4ec59f2ae..3363a560cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenuContext.kt @@ -17,5 +17,5 @@ public class EphemeralChannelSelectMenuContext( override val component: EphemeralChannelSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: EphemeralMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : ChannelSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt index 1eb617009a..ca9b10bd27 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt @@ -6,18 +6,14 @@ package com.kotlindiscord.kord.extensions.components.menus.channel -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.common.entity.ChannelType +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSelectMenuResponseBuilder = @@ -27,103 +23,16 @@ public typealias InitialPublicSelectMenuResponseBuilder = public open class PublicChannelSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : ChannelSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondPublic { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferPublicResponseUnsafe() - } else { - it?.deferPublicMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferPublicResponseUnsafe() - } else { - event.interaction.deferPublicMessageUpdate() - } - } - - val context = PublicChannelSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: PublicChannelSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : PublicSelectMenu, M>(timeoutTask), ChannelSelectMenu { + override var channelTypes: MutableList = mutableListOf() + + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): PublicChannelSelectMenuContext = PublicChannelSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyChannelSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt index 124059be45..8405d7291b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenuContext.kt @@ -17,5 +17,5 @@ public class PublicChannelSelectMenuContext( override val component: PublicChannelSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: PublicMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : ChannelSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt index 386bc4cd2b..e8d256db5d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt @@ -6,17 +6,13 @@ package com.kotlindiscord.kord.extensions.components.menus.role -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = @@ -26,103 +22,14 @@ public typealias InitialEphemeralSelectMenuResponseBuilder = public open class EphemeralRoleSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : RoleSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondEphemeral { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferEphemeralResponseUnsafe() - } else { - it?.deferEphemeralMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferEphemeralResponseUnsafe() - } else { - event.interaction.deferEphemeralMessageUpdate() - } - } - - val context = EphemeralRoleSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: EphemeralRoleSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : EphemeralSelectMenu, M>(timeoutTask), RoleSelectMenu { + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): EphemeralRoleSelectMenuContext = EphemeralRoleSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyRoleSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt index c87e63d813..7feb6482d7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt @@ -17,5 +17,5 @@ public class EphemeralRoleSelectMenuContext( override val component: EphemeralRoleSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: EphemeralMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : RoleSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt index 7925f19930..f02658c8ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.kt @@ -6,18 +6,13 @@ package com.kotlindiscord.kord.extensions.components.menus.role -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSelectMenuResponseBuilder = @@ -27,103 +22,14 @@ public typealias InitialPublicSelectMenuResponseBuilder = public open class PublicRoleSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : RoleSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondPublic { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferPublicResponseUnsafe() - } else { - it?.deferPublicMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferPublicResponseUnsafe() - } else { - event.interaction.deferPublicMessageUpdate() - } - } - - val context = PublicRoleSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: PublicRoleSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : PublicSelectMenu, M>(timeoutTask), RoleSelectMenu { + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): PublicRoleSelectMenuContext = PublicRoleSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyRoleSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt index 0f8d1aedf2..aed04315c8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenuContext.kt @@ -17,5 +17,5 @@ public class PublicRoleSelectMenuContext( override val component: PublicRoleSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: PublicMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : RoleSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt index 89c8ba5241..b14c9e08ba 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt @@ -1,27 +1,20 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - package com.kotlindiscord.kord.extensions.components.menus.role -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.menus.* -import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import dev.kord.rest.builder.component.ActionRowBuilder -/** Abstract class representing a role select (dropdown) menu component. **/ -public abstract class RoleSelectMenu(timeoutTask: Task?) : - SelectMenu(timeoutTask) { +/** Interface for role select menus. **/ +public interface RoleSelectMenu { - public override fun apply(builder: ActionRowBuilder) { - if (maximumChoices == null) maximumChoices = OPTIONS_MAX + /** Apply the role select menu to an action row builder. **/ + public fun applyRoleSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) { + if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX - builder.roleSelect(id) { - this.allowedValues = minimumChoices..maximumChoices!! - this.placeholder = this@RoleSelectMenu.placeholder - this.disabled = this@RoleSelectMenu.disabled + builder.roleSelect(selectMenu.id) { + this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! + this.placeholder = selectMenu.placeholder + this.disabled = selectMenu.disabled } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt index 2bd0a073d0..a23d651666 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenuContext.kt @@ -6,7 +6,7 @@ package com.kotlindiscord.kord.extensions.components.menus.role -import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.common.annotation.KordExperimental @@ -17,7 +17,7 @@ import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a role select (dropdown) menu component. **/ public abstract class RoleSelectMenuContext( - component: Component, + component: SelectMenu<*, *>, event: SelectMenuInteractionCreateEvent, cache: MutableStringKeyedMap, ) : SelectMenuContext(component, event, cache) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt index 3d39a7f478..012dbce302 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt @@ -6,17 +6,14 @@ package com.kotlindiscord.kord.extensions.components.menus.string -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.component.SelectOptionBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = @@ -26,103 +23,16 @@ public typealias InitialEphemeralSelectMenuResponseBuilder = public open class EphemeralStringSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : StringSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondEphemeral { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferEphemeralResponseUnsafe() - } else { - it?.deferEphemeralMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferEphemeralResponseUnsafe() - } else { - event.interaction.deferEphemeralMessageUpdate() - } - } - - val context = EphemeralStringSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: EphemeralStringSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : EphemeralSelectMenu, M>(timeoutTask), StringSelectMenu { + override val options: MutableList = mutableListOf() + + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): EphemeralStringSelectMenuContext = EphemeralStringSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyStringSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt index 605942a33e..a533bc5883 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenuContext.kt @@ -17,5 +17,5 @@ public class EphemeralStringSelectMenuContext( override val component: EphemeralStringSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: EphemeralMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : StringSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt index f432fe39f3..28083c12fb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt @@ -6,18 +6,14 @@ package com.kotlindiscord.kord.extensions.components.menus.string -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.component.SelectOptionBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSelectMenuResponseBuilder = @@ -27,103 +23,16 @@ public typealias InitialPublicSelectMenuResponseBuilder = public open class PublicStringSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : StringSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondPublic { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferPublicResponseUnsafe() - } else { - it?.deferPublicMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferPublicResponseUnsafe() - } else { - event.interaction.deferPublicMessageUpdate() - } - } - - val context = PublicStringSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: PublicStringSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : PublicSelectMenu, M>(timeoutTask), StringSelectMenu { + override val options: MutableList = mutableListOf() + + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): PublicStringSelectMenuContext = PublicStringSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyStringSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt index 5c19eeee6f..16a124d5e7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt @@ -17,5 +17,5 @@ public class PublicStringSelectMenuContext( override val component: PublicStringSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: PublicMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : StringSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt index 4fddd66ae4..8ec80ddd72 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt @@ -1,27 +1,17 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - package com.kotlindiscord.kord.extensions.components.menus.string -import com.kotlindiscord.kord.extensions.components.forms.ModalForm import com.kotlindiscord.kord.extensions.components.menus.* -import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.component.SelectOptionBuilder -/** Abstract class representing a string select (dropdown) menu component. **/ -public abstract class StringSelectMenu(timeoutTask: Task?) : - SelectMenu(timeoutTask) { - +/** Interface for string select menus. **/ +public interface StringSelectMenu { /** List of options for the user to choose from. **/ - public val options: MutableList = mutableListOf() + public val options: MutableList /** Add an option to this select menu. **/ @Suppress("UnnecessaryParentheses") - public open suspend fun option( + public suspend fun option( label: String, value: String, @@ -50,26 +40,25 @@ public abstract class StringSelectMenu options.size) { - maximumChoices = options.size + /** Apply the string select menu to an action row builder. **/ + public fun applyStringSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) { + if (selectMenu.maximumChoices == null || selectMenu.maximumChoices!! > options.size) { + selectMenu.maximumChoices = options.size } - builder.stringSelect(id) { - this.allowedValues = minimumChoices..maximumChoices!! + builder.stringSelect(selectMenu.id) { + this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! @Suppress("DEPRECATION") // Kord suppresses this in their own class this.options.addAll(this@StringSelectMenu.options) - this.placeholder = this@StringSelectMenu.placeholder - - this.disabled = this@StringSelectMenu.disabled + this.placeholder = selectMenu.placeholder + this.disabled = selectMenu.disabled } } + /** Validate the options of the string select menu. **/ @Suppress("UnnecessaryParentheses") - override fun validate() { - super.validate() - + public fun validateOptions() { if (this.options.isEmpty()) { error("Menu components must have at least one option.") } @@ -77,9 +66,5 @@ public abstract class StringSelectMenu OPTIONS_MAX) { error("Menu components must not have more than $OPTIONS_MAX options.") } - - if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) { - error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") - } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt index 1b868a11af..77c5b24b87 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenuContext.kt @@ -6,14 +6,14 @@ package com.kotlindiscord.kord.extensions.components.menus.string -import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a string select (dropdown) menu component. **/ public abstract class StringSelectMenuContext( - component: Component, + component: SelectMenu<*, *>, event: SelectMenuInteractionCreateEvent, cache: MutableStringKeyedMap, ) : SelectMenuContext(component, event, cache) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt index dbed401bb4..b3a7384d69 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.kt @@ -6,17 +6,13 @@ package com.kotlindiscord.kord.extensions.components.menus.user -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = @@ -26,103 +22,14 @@ public typealias InitialEphemeralSelectMenuResponseBuilder = public open class EphemeralUserSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : UserSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondEphemeral { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferEphemeralResponseUnsafe() - } else { - it?.deferEphemeralMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferEphemeralResponseUnsafe() - } else { - event.interaction.deferEphemeralMessageUpdate() - } - } - - val context = EphemeralUserSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: EphemeralUserSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : EphemeralSelectMenu, M>(timeoutTask), UserSelectMenu { + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: EphemeralMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): EphemeralUserSelectMenuContext = EphemeralUserSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyUserSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt index ed16f1afbc..eeaaca34ac 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenuContext.kt @@ -17,5 +17,5 @@ public class EphemeralUserSelectMenuContext( override val component: EphemeralUserSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: EphemeralMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : UserSelectMenuContext(component, event, cache), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt index 4ec1aadd52..dbdb77699b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.kt @@ -6,18 +6,13 @@ package com.kotlindiscord.kord.extensions.components.menus.user -import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.types.FailureReason -import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap -import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.scheduling.Task -import dev.kord.common.annotation.KordUnsafe -import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSelectMenuResponseBuilder = @@ -27,103 +22,14 @@ public typealias InitialPublicSelectMenuResponseBuilder = public open class PublicUserSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : UserSelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null - - /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { - initialResponseBuilder = body - } - - override fun validate() { - super.validate() - - if (modal != null && initialResponseBuilder != null) { - error("You may not provide a modal builder and an initial response - pick one, not both.") - } - } - - @OptIn(KordUnsafe::class) - @Suppress("TooGenericExceptionCaught") - override suspend fun call(event: SelectMenuInteractionCreateEvent): Unit = withLock { - val cache: MutableStringKeyedMap = mutableMapOf() - - super.call(event) - - try { - if (!runChecks(event, cache)) { - return@withLock - } - } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { - settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) - } - - return@withLock - } - - val modalObj = modal?.invoke() - - val response = if (initialResponseBuilder != null) { - event.interaction.respondPublic { initialResponseBuilder!!(event) } - } else if (modalObj != null) { - componentRegistry.register(modalObj) - - val locale = event.getLocale() - - event.interaction.modal( - modalObj.translateTitle(locale, bundle), - modalObj.id - ) { - modalObj.applyToBuilder(this, event.getLocale(), bundle) - } - - modalObj.awaitCompletion { - componentRegistry.unregisterModal(modalObj) - - if (!deferredAck) { - it?.deferPublicResponseUnsafe() - } else { - it?.deferPublicMessageUpdate() - } - } ?: return@withLock - } else { - if (!deferredAck) { - event.interaction.deferPublicResponseUnsafe() - } else { - event.interaction.deferPublicMessageUpdate() - } - } - - val context = PublicUserSelectMenuContext(this, event, response, cache) - - context.populate() - - firstSentryBreadcrumb(context, this) - - try { - checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) - - return@withLock - } - - try { - body(context, modalObj) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason, FailureReason.RelayedFailure(e)) - } catch (t: Throwable) { - handleError(context, t, this) - } - } - - override suspend fun respondText( - context: PublicUserSelectMenuContext, - message: String, - failureType: FailureReason<*> - ) { - context.respond { settings.failureResponseBuilder(this, message, failureType) } - } +) : PublicSelectMenu, M>(timeoutTask), UserSelectMenu { + override fun createContext( + event: SelectMenuInteractionCreateEvent, + interactionResponse: PublicMessageInteractionResponseBehavior, + cache: MutableStringKeyedMap, + ): PublicUserSelectMenuContext = PublicUserSelectMenuContext( + this, event, interactionResponse, cache + ) + + override fun apply(builder: ActionRowBuilder): Unit = applyUserSelectMenu(this, builder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt index 8c7f609c1e..7f0bbab6ad 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenuContext.kt @@ -17,5 +17,5 @@ public class PublicUserSelectMenuContext( override val component: PublicUserSelectMenu, override val event: SelectMenuInteractionCreateEvent, override val interactionResponse: PublicMessageInteractionResponseBehavior, - cache: MutableStringKeyedMap + cache: MutableStringKeyedMap, ) : UserSelectMenuContext(component, event, cache), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt index 34978e10a7..7a5b30c422 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt @@ -1,27 +1,20 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - package com.kotlindiscord.kord.extensions.components.menus.user -import com.kotlindiscord.kord.extensions.components.forms.ModalForm -import com.kotlindiscord.kord.extensions.components.menus.* -import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import dev.kord.rest.builder.component.ActionRowBuilder -/** Abstract class representing a user select (dropdown) menu component. **/ -public abstract class UserSelectMenu(timeoutTask: Task?) : - SelectMenu(timeoutTask) { +/** Interface for user select menus. **/ +public interface UserSelectMenu { - public override fun apply(builder: ActionRowBuilder) { - if (maximumChoices == null) maximumChoices = OPTIONS_MAX + /** Apply the user select menu to an action row builder. **/ + public fun applyUserSelectMenu(selectMenu: SelectMenu<*, *>, builder: ActionRowBuilder) { + if (selectMenu.maximumChoices == null) selectMenu.maximumChoices = OPTIONS_MAX - builder.userSelect(id) { - this.allowedValues = minimumChoices..maximumChoices!! - this.placeholder = this@UserSelectMenu.placeholder - this.disabled = this@UserSelectMenu.disabled + builder.userSelect(selectMenu.id) { + this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! + this.placeholder = selectMenu.placeholder + this.disabled = selectMenu.disabled } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt index 95ad80895f..c8ed620294 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenuContext.kt @@ -6,7 +6,7 @@ package com.kotlindiscord.kord.extensions.components.menus.user -import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu import com.kotlindiscord.kord.extensions.components.menus.SelectMenuContext import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap import dev.kord.common.annotation.KordExperimental @@ -17,7 +17,7 @@ import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent /** Abstract class representing the execution context of a user select (dropdown) menu component. **/ public abstract class UserSelectMenuContext( - component: Component, + component: SelectMenu<*, *>, event: SelectMenuInteractionCreateEvent, cache: MutableStringKeyedMap, ) : SelectMenuContext(component, event, cache) { diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt index 3cd67aa6a4..ef7bdcf3ce 100644 --- a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt @@ -6,6 +6,8 @@ package com.kotlindiscord.kord.extensions.testbot.extensions +import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.components.* import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand @@ -23,63 +25,138 @@ public class SelectorTestExtension : Extension() { name = "selector" description = "Test selectors." - action { - respond { - components { - publicStringSelectMenu { - option("hi", "1") - option("hi hi", "2") - maximumChoices = null + publicSubCommand { + name = "public" + description = "Test public selectors." - action { - respond { content = selected.joinToString("\n") } + action { + respond { + components { + publicStringSelectMenu { + option("hi", "1") + option("hi hi", "2") + maximumChoices = null + + action { + respond { content = selected.joinToString("\n") } + } } - } - publicUserSelectMenu { - maximumChoices = null + publicUserSelectMenu { + maximumChoices = null - action { - respond { - content = selected.joinToString("\n") { - runBlocking { it.asUser().username } + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asUser().username } + } } } } - } - publicRoleSelectMenu { - maximumChoices = null + publicRoleSelectMenu { + maximumChoices = null - action { - respond { - content = selected.joinToString("\n") { - runBlocking { it.asRole().name } + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asRole().name } + } } } } - } - publicChannelSelectMenu { - maximumChoices = null + publicChannelSelectMenu { + maximumChoices = null - action { - respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannel().id.value.toString() } + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannel().id.value.toString() } + } + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannelOf().name } + } } } } } + } + } + } + + ephemeralSubCommand { + name = "ephemeral" + description = "Test ephemeral selectors." + + action { + respond { + components { + ephemeralStringSelectMenu { + option("hi", "1") + option("hi hi", "2") + maximumChoices = null + + action { + respond { content = selected.joinToString("\n") } + } + } + + ephemeralUserSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asUser().username } + } + } + } + } + + ephemeralRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asRole().name } + } + } + } + } + + ephemeralChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannel().id.value.toString() } + } + } + } + } - publicChannelSelectMenu { - maximumChoices = null - channelType(ChannelType.GuildText) + ephemeralChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) - action { - respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannelOf().name } + action { + respond { + content = selected.joinToString("\n") { + runBlocking { it.asChannelOf().name } + } } } } From ea1de14fe5bf9ce3ee7078ba7f286932296d98a6 Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:31:07 +0100 Subject: [PATCH 3/7] licenses --- .../components/menus/channel/ChannelSelectMenu.kt | 6 ++++++ .../kord/extensions/components/menus/role/RoleSelectMenu.kt | 6 ++++++ .../extensions/components/menus/string/StringSelectMenu.kt | 6 ++++++ .../kord/extensions/components/menus/user/UserSelectMenu.kt | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt index ded9a0abd3..594558bec1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt @@ -1,3 +1,9 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + package com.kotlindiscord.kord.extensions.components.menus.channel import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt index b14c9e08ba..e100271562 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt @@ -1,3 +1,9 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + package com.kotlindiscord.kord.extensions.components.menus.role import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt index 8ec80ddd72..4c45880cd9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt @@ -1,3 +1,9 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + package com.kotlindiscord.kord.extensions.components.menus.string import com.kotlindiscord.kord.extensions.components.menus.* diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt index 7a5b30c422..42333c981b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt @@ -1,3 +1,9 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + package com.kotlindiscord.kord.extensions.components.menus.user import com.kotlindiscord.kord.extensions.components.menus.OPTIONS_MAX From 90683d5e4f52f65f0f5de33a0af01726fbba3dad Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:20:35 +0200 Subject: [PATCH 4/7] merge changes --- .../extensions/components/menus/string/StringSelectMenu.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt index 4c45880cd9..8749b0cba0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt @@ -9,6 +9,8 @@ package com.kotlindiscord.kord.extensions.components.menus.string import com.kotlindiscord.kord.extensions.components.menus.* import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.component.SelectOptionBuilder +import dev.kord.rest.builder.component.StringSelectBuilder +import dev.kord.rest.builder.component.options /** Interface for string select menus. **/ public interface StringSelectMenu { @@ -54,9 +56,7 @@ public interface StringSelectMenu { builder.stringSelect(selectMenu.id) { this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! - - @Suppress("DEPRECATION") // Kord suppresses this in their own class - this.options.addAll(this@StringSelectMenu.options) + ((this as? StringSelectBuilder)?.options ?: mutableListOf()).addAll(this@StringSelectMenu.options) this.placeholder = selectMenu.placeholder this.disabled = selectMenu.disabled } From 1b625cf505b3c9bb0a8792508a115a54daf5cf2a Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:47:45 +0200 Subject: [PATCH 5/7] add back old methods --- .idea/runConfigurations.xml | 10 -- .../kord/extensions/components/_Functions.kt | 92 ++++++++++++++----- 2 files changed, 67 insertions(+), 35 deletions(-) delete mode 100644 .idea/runConfigurations.xml diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 797acea53e..0000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt index c3dbc478a7..fd58c55c4d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt @@ -26,7 +26,7 @@ import kotlin.time.Duration /** DSL function for creating a disabled button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.disabledButton( row: Int? = null, - builder: suspend DisabledInteractionButton.() -> Unit + builder: suspend DisabledInteractionButton.() -> Unit, ): DisabledInteractionButton { val component = DisabledInteractionButton() @@ -39,7 +39,7 @@ public suspend fun ComponentContainer.disabledButton( /** DSL function for creating an ephemeral button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralButton( row: Int? = null, - builder: suspend EphemeralInteractionButton.() -> Unit + builder: suspend EphemeralInteractionButton.() -> Unit, ): EphemeralInteractionButton { val component = EphemeralInteractionButton(timeoutTask) @@ -53,7 +53,7 @@ public suspend fun ComponentContainer.ephemeralButton( public suspend fun ComponentContainer.ephemeralButton( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralInteractionButton.() -> Unit + builder: suspend EphemeralInteractionButton.() -> Unit, ): EphemeralInteractionButton { val component = EphemeralInteractionButton(timeoutTask, modal) @@ -66,7 +66,7 @@ public suspend fun ComponentContainer.ephemeralButton( /** DSL function for creating a link button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.linkButton( row: Int? = null, - builder: suspend LinkInteractionButton.() -> Unit + builder: suspend LinkInteractionButton.() -> Unit, ): LinkInteractionButton { val component = LinkInteractionButton() @@ -79,7 +79,7 @@ public suspend fun ComponentContainer.linkButton( /** DSL function for creating a public button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicButton( row: Int? = null, - builder: suspend PublicInteractionButton.() -> Unit + builder: suspend PublicInteractionButton.() -> Unit, ): PublicInteractionButton { val component = PublicInteractionButton(timeoutTask) @@ -93,7 +93,7 @@ public suspend fun ComponentContainer.publicButton( public suspend fun ComponentContainer.publicButton( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicInteractionButton.() -> Unit + builder: suspend PublicInteractionButton.() -> Unit, ): PublicInteractionButton { val component = PublicInteractionButton(timeoutTask, modal) @@ -103,10 +103,31 @@ public suspend fun ComponentContainer.publicButton( return component } +/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/ +@Deprecated( + message = "Deprecated to allow other option types.", + replaceWith = ReplaceWith("this.ephemeralStringSelectMenu(row, builder)") +) +public suspend fun ComponentContainer.ephemeralSelectMenu( + row: Int? = null, + builder: suspend EphemeralStringSelectMenu.() -> Unit, +): EphemeralStringSelectMenu = ephemeralStringSelectMenu(row, builder) + +/** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/ +@Deprecated( + message = "Deprecated to allow other option types.", + replaceWith = ReplaceWith("this.ephemeralStringSelectMenu(modal, row, builder)") +) +public suspend fun ComponentContainer.ephemeralSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend EphemeralStringSelectMenu.() -> Unit, +): EphemeralStringSelectMenu = ephemeralStringSelectMenu(modal, row, builder) + /** DSL function for creating an ephemeral string select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralStringSelectMenu( row: Int? = null, - builder: suspend EphemeralStringSelectMenu.() -> Unit + builder: suspend EphemeralStringSelectMenu.() -> Unit, ): EphemeralStringSelectMenu { val component = EphemeralStringSelectMenu(timeoutTask) @@ -120,9 +141,9 @@ public suspend fun ComponentContainer.ephemeralStringSelectMenu( public suspend fun ComponentContainer.ephemeralStringSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralStringSelectMenu.() -> Unit + builder: suspend EphemeralStringSelectMenu.() -> Unit, ): EphemeralStringSelectMenu { - val component = EphemeralStringSelectMenu(timeoutTask, modal) + val component = EphemeralStringSelectMenu(timeoutTask, modal) builder(component) add(component, row) @@ -130,10 +151,31 @@ public suspend fun ComponentContainer.ephemeralStringSelectMenu( return component } +/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/ +@Deprecated( + message = "Deprecated to allow other option types.", + replaceWith = ReplaceWith("this.publicStringSelectMenu(row, builder)") +) +public suspend fun ComponentContainer.publicSelectMenu( + row: Int? = null, + builder: suspend PublicStringSelectMenu.() -> Unit, +): PublicStringSelectMenu = publicStringSelectMenu(row, builder) + +/** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/ +@Deprecated( + message = "Deprecated to allow other option types.", + replaceWith = ReplaceWith("this.publicStringSelectMenu(modal, row, builder)") +) +public suspend fun ComponentContainer.publicSelectMenu( + modal: (() -> M)?, + row: Int? = null, + builder: suspend PublicStringSelectMenu.() -> Unit, +): PublicStringSelectMenu = publicStringSelectMenu(modal, row, builder) + /** DSL function for creating a public string select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicStringSelectMenu( row: Int? = null, - builder: suspend PublicStringSelectMenu.() -> Unit + builder: suspend PublicStringSelectMenu.() -> Unit, ): PublicStringSelectMenu { val component = PublicStringSelectMenu(timeoutTask) @@ -147,7 +189,7 @@ public suspend fun ComponentContainer.publicStringSelectMenu( public suspend fun ComponentContainer.publicStringSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicStringSelectMenu.() -> Unit + builder: suspend PublicStringSelectMenu.() -> Unit, ): PublicStringSelectMenu { val component = PublicStringSelectMenu(timeoutTask, modal) @@ -160,7 +202,7 @@ public suspend fun ComponentContainer.publicStringSelectMenu( /** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralUserSelectMenu( row: Int? = null, - builder: suspend EphemeralUserSelectMenu.() -> Unit + builder: suspend EphemeralUserSelectMenu.() -> Unit, ): EphemeralUserSelectMenu { val component = EphemeralUserSelectMenu(timeoutTask) @@ -174,7 +216,7 @@ public suspend fun ComponentContainer.ephemeralUserSelectMenu( public suspend fun ComponentContainer.ephemeralUserSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralUserSelectMenu.() -> Unit + builder: suspend EphemeralUserSelectMenu.() -> Unit, ): EphemeralUserSelectMenu { val component = EphemeralUserSelectMenu(timeoutTask, modal) @@ -187,7 +229,7 @@ public suspend fun ComponentContainer.ephemeralUserSelectMenu( /** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicUserSelectMenu( row: Int? = null, - builder: suspend PublicUserSelectMenu.() -> Unit + builder: suspend PublicUserSelectMenu.() -> Unit, ): PublicUserSelectMenu { val component = PublicUserSelectMenu(timeoutTask) @@ -201,7 +243,7 @@ public suspend fun ComponentContainer.publicUserSelectMenu( public suspend fun ComponentContainer.publicUserSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicUserSelectMenu.() -> Unit + builder: suspend PublicUserSelectMenu.() -> Unit, ): PublicUserSelectMenu { val component = PublicUserSelectMenu(timeoutTask, modal) @@ -214,7 +256,7 @@ public suspend fun ComponentContainer.publicUserSelectMenu( /** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralRoleSelectMenu( row: Int? = null, - builder: suspend EphemeralRoleSelectMenu.() -> Unit + builder: suspend EphemeralRoleSelectMenu.() -> Unit, ): EphemeralRoleSelectMenu { val component = EphemeralRoleSelectMenu(timeoutTask) @@ -228,7 +270,7 @@ public suspend fun ComponentContainer.ephemeralRoleSelectMenu( public suspend fun ComponentContainer.ephemeralRoleSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralRoleSelectMenu.() -> Unit + builder: suspend EphemeralRoleSelectMenu.() -> Unit, ): EphemeralRoleSelectMenu { val component = EphemeralRoleSelectMenu(timeoutTask, modal) @@ -241,7 +283,7 @@ public suspend fun ComponentContainer.ephemeralRoleSelectMenu( /** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicRoleSelectMenu( row: Int? = null, - builder: suspend PublicRoleSelectMenu.() -> Unit + builder: suspend PublicRoleSelectMenu.() -> Unit, ): PublicRoleSelectMenu { val component = PublicRoleSelectMenu(timeoutTask) @@ -255,7 +297,7 @@ public suspend fun ComponentContainer.publicRoleSelectMenu( public suspend fun ComponentContainer.publicRoleSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicRoleSelectMenu.() -> Unit + builder: suspend PublicRoleSelectMenu.() -> Unit, ): PublicRoleSelectMenu { val component = PublicRoleSelectMenu(timeoutTask, modal) @@ -268,7 +310,7 @@ public suspend fun ComponentContainer.publicRoleSelectMenu( /** DSL function for creating an ephemeral user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralChannelSelectMenu( row: Int? = null, - builder: suspend EphemeralChannelSelectMenu.() -> Unit + builder: suspend EphemeralChannelSelectMenu.() -> Unit, ): EphemeralChannelSelectMenu { val component = EphemeralChannelSelectMenu(timeoutTask) @@ -282,7 +324,7 @@ public suspend fun ComponentContainer.ephemeralChannelSelectMenu( public suspend fun ComponentContainer.ephemeralChannelSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend EphemeralChannelSelectMenu.() -> Unit + builder: suspend EphemeralChannelSelectMenu.() -> Unit, ): EphemeralChannelSelectMenu { val component = EphemeralChannelSelectMenu(timeoutTask, modal) @@ -295,7 +337,7 @@ public suspend fun ComponentContainer.ephemeralChannelSelectMenu /** DSL function for creating a public user select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicChannelSelectMenu( row: Int? = null, - builder: suspend PublicChannelSelectMenu.() -> Unit + builder: suspend PublicChannelSelectMenu.() -> Unit, ): PublicChannelSelectMenu { val component = PublicChannelSelectMenu(timeoutTask) @@ -309,7 +351,7 @@ public suspend fun ComponentContainer.publicChannelSelectMenu( public suspend fun ComponentContainer.publicChannelSelectMenu( modal: (() -> M)?, row: Int? = null, - builder: suspend PublicChannelSelectMenu.() -> Unit + builder: suspend PublicChannelSelectMenu.() -> Unit, ): PublicChannelSelectMenu { val component = PublicChannelSelectMenu(timeoutTask, modal) @@ -340,7 +382,7 @@ public suspend fun MessageModifyBuilder.applyComponents(components: ComponentCon */ public suspend fun MessageCreateBuilder.components( timeout: Duration? = null, - builder: suspend ComponentContainer.() -> Unit + builder: suspend ComponentContainer.() -> Unit, ): ComponentContainer { val container = ComponentContainer(timeout, true, builder) @@ -356,7 +398,7 @@ public suspend fun MessageCreateBuilder.components( */ public suspend fun MessageModifyBuilder.components( timeout: Duration? = null, - builder: suspend ComponentContainer.() -> Unit + builder: suspend ComponentContainer.() -> Unit, ): ComponentContainer { val container = ComponentContainer(timeout, true, builder) From 695efd18fdaea13d771f6190e3553c35ccb4d682 Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:58:04 +0200 Subject: [PATCH 6/7] small changes --- .../components/menus/EphemeralSelectMenu.kt | 2 +- .../components/menus/PublicSelectMenu.kt | 2 +- .../extensions/components/menus/SelectMenu.kt | 9 ----- .../menus/string/StringSelectMenu.kt | 9 +++++ .../extensions/SelectorTestExtension.kt | 33 +++++-------------- 5 files changed, 19 insertions(+), 36 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index 3bbb37514f..4c6ff3067d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -21,7 +21,7 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.response.EphemeralMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -/** Interface for ephemeral select menus. **/ +/** Class representing an ephemeral-only select (dropdown) menu. **/ public abstract class EphemeralSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt index 5fa1b61559..29b603b0c5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -22,7 +22,7 @@ import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -/** Class for public select menus. **/ +/** Class representing a public-only select (dropdown) menu. **/ public abstract class PublicSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index 626ea50876..d916d6d92e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -19,21 +19,12 @@ import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging -/** Maximum length for an option's description. **/ -public const val DESCRIPTION_MAX: Int = 100 - -/** Maximum length for an option's label. **/ -public const val LABEL_MAX: Int = 100 - /** Maximum number of options for a menu. **/ public const val OPTIONS_MAX: Int = 25 /** Maximum length for a menu's placeholder. **/ public const val PLACEHOLDER_MAX: Int = 100 -/** Maximum length for an option's value. **/ -public const val VALUE_MAX: Int = 100 - /** Abstract class representing a select (dropdown) menu component. **/ public abstract class SelectMenu( timeoutTask: Task?, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt index 8749b0cba0..d5babab887 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/StringSelectMenu.kt @@ -12,6 +12,15 @@ import dev.kord.rest.builder.component.SelectOptionBuilder import dev.kord.rest.builder.component.StringSelectBuilder import dev.kord.rest.builder.component.options +/** Maximum length for an option's description. **/ +public const val DESCRIPTION_MAX: Int = 100 + +/** Maximum length for an option's label. **/ +public const val LABEL_MAX: Int = 100 + +/** Maximum length for an option's value. **/ +public const val VALUE_MAX: Int = 100 + /** Interface for string select menus. **/ public interface StringSelectMenu { /** List of options for the user to choose from. **/ diff --git a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt index ef7bdcf3ce..c69e1d0db6 100644 --- a/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt @@ -15,7 +15,6 @@ import com.kotlindiscord.kord.extensions.types.respond import dev.kord.common.entity.ChannelType import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.entity.channel.TextChannel -import kotlinx.coroutines.runBlocking public class SelectorTestExtension : Extension() { override val name: String = "Select Menus Test" @@ -47,9 +46,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asUser().username } - } + content = selected.map { it.asUser().username }.joinToString("\n") } } } @@ -59,9 +56,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asRole().name } - } + content = selected.map { it.asRole().name }.joinToString("\n") } } } @@ -71,9 +66,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannel().id.value.toString() } - } + content = selected.map { it.id.value }.joinToString("\n") } } } @@ -84,9 +77,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannelOf().name } - } + content = selected.map { it.asChannelOf().name }.joinToString("\n") } } } @@ -117,9 +108,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asUser().username } - } + content = selected.map { it.asUser().username }.joinToString("\n") } } } @@ -129,9 +118,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asRole().name } - } + content = selected.map { it.asRole().name }.joinToString("\n") } } } @@ -141,9 +128,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannel().id.value.toString() } - } + content = selected.map { it.id.value }.joinToString("\n") } } } @@ -154,9 +139,7 @@ public class SelectorTestExtension : Extension() { action { respond { - content = selected.joinToString("\n") { - runBlocking { it.asChannelOf().name } - } + content = selected.map { it.asChannelOf().name }.joinToString("\n") } } } From 14acec96b5a2e4fe3ed4b11ce477caeba628865a Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:43:18 +0200 Subject: [PATCH 7/7] undo delete runConfigurations.xml --- .idea/runConfigurations.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .idea/runConfigurations.xml diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000000..a1fe99eb3b --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + +