diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml similarity index 96% rename from .idea/runConfigurations.xml rename to .idea/runConfigurations.xml index 797acea53e..a1fe99eb3b 100644 --- a/.idea/runConfigurations.xml +++ b/.idea/runConfigurations.xml @@ -7,4 +7,4 @@ - \ 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 98a094dd09..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 @@ -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 @@ -20,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() @@ -33,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) @@ -47,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) @@ -60,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() @@ -73,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) @@ -87,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) @@ -97,12 +103,33 @@ public suspend fun ComponentContainer.publicButton( return component } -/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/ +/** 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 EphemeralSelectMenu.() -> Unit -): EphemeralSelectMenu { - val component = EphemeralSelectMenu(timeoutTask) + 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, +): EphemeralStringSelectMenu { + val component = EphemeralStringSelectMenu(timeoutTask) builder(component) add(component, row) @@ -110,13 +137,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 +151,33 @@ public suspend fun ComponentContainer.ephemeralSelectMenu( return component } -/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/ +/** 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 PublicSelectMenu.() -> Unit -): PublicSelectMenu { - val component = PublicSelectMenu(timeoutTask) + 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, +): PublicStringSelectMenu { + val component = PublicStringSelectMenu(timeoutTask) builder(component) add(component, row) @@ -137,13 +185,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 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 PublicSelectMenu.() -> Unit -): PublicSelectMenu { - val component = PublicSelectMenu(timeoutTask, modal) + builder: suspend PublicChannelSelectMenu.() -> Unit, +): PublicChannelSelectMenu { + val component = PublicChannelSelectMenu(timeoutTask, modal) builder(component) add(component, row) @@ -172,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) @@ -188,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) 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 d572430421..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 @@ -4,13 +4,12 @@ * 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 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 @@ -19,19 +18,16 @@ 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.message.create.InteractionResponseCreateBuilder - -public typealias InitialEphemeralSelectMenuResponseBuilder = - (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? /** Class representing an ephemeral-only select (dropdown) menu. **/ -public open class EphemeralSelectMenu( +public abstract class EphemeralSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : SelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = 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) { @@ -46,6 +42,15 @@ public open class EphemeralSelectMenu( } } + /** 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() @@ -96,7 +101,7 @@ public open class EphemeralSelectMenu( } } - val context = EphemeralSelectMenuContext(this, event, response, cache) + val context = createContext(event, response, cache) context.populate() @@ -120,9 +125,9 @@ public open class EphemeralSelectMenu( } override suspend fun respondText( - context: EphemeralSelectMenuContext, + context: C, message: String, - failureType: FailureReason<*> + 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 index 4ef24a0333..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 @@ -4,14 +4,13 @@ * 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 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 @@ -20,22 +19,19 @@ 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.message.create.InteractionResponseCreateBuilder - -public typealias InitialPublicSelectMenuResponseBuilder = - (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? /** Class representing a public-only select (dropdown) menu. **/ -public open class PublicSelectMenu( +public abstract class PublicSelectMenu( timeoutTask: Task?, public override val modal: (() -> M)? = null, -) : SelectMenu, M>(timeoutTask) { - /** @suppress Initial response builder. **/ - public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = 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: InitialPublicSelectMenuResponseBuilder) { + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { initialResponseBuilder = body } @@ -47,6 +43,15 @@ public open class PublicSelectMenu( } } + /** 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() @@ -97,7 +102,7 @@ public open class PublicSelectMenu( } } - val context = PublicSelectMenuContext(this, event, response, cache) + val context = createContext(event, response, cache) context.populate() @@ -121,9 +126,9 @@ public open class PublicSelectMenu( } override suspend fun respondText( - context: PublicSelectMenuContext, + context: C, message: String, - failureType: FailureReason<*> + 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 32f95725d7..1aa21ebf9c 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,11 +15,9 @@ 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.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging /** Maximum length for an option's description. **/ public const val DESCRIPTION_MAX: Int = 100 @@ -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,65 +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, - - body: (suspend SelectOptionBuilder.() -> Unit) = {}, - ) { - val builder = SelectOptionBuilder(label, value) - - 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!! - - 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) { @@ -186,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 7b65dc24bb..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,17 +6,14 @@ 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 /** 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 -) : 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 } -} + 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 new file mode 100644 index 0000000000..594558bec1 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/ChannelSelectMenu.kt @@ -0,0 +1,39 @@ +/* + * 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 +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu +import dev.kord.common.entity.ChannelType +import dev.kord.rest.builder.component.ActionRowBuilder + +/** 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) + } + + /** 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(selectMenu.id) { + this.channelTypes = if (this@ChannelSelectMenu.channelTypes.isEmpty()) { + null + } else { + this@ChannelSelectMenu.channelTypes + } + 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 new file mode 100644 index 0000000000..acadd11d84 --- /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.menus.SelectMenu +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: SelectMenu<*, *>, + 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..8209df30fc --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/EphemeralChannelSelectMenu.kt @@ -0,0 +1,38 @@ +/* + * 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.EphemeralSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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 new file mode 100644 index 0000000000..3363a560cb --- /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..ca9b10bd27 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/channel/PublicChannelSelectMenu.kt @@ -0,0 +1,38 @@ +/* + * 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.PublicSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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 new file mode 100644 index 0000000000..8405d7291b --- /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/role/EphemeralRoleSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.kt new file mode 100644 index 0000000000..e8d256db5d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenu.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.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing an ephemeral-only role select (dropdown) menu. **/ +public open class EphemeralRoleSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : 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/EphemeralSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/EphemeralRoleSelectMenuContext.kt similarity index 70% 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..7feb6482d7 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 + 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 new file mode 100644 index 0000000000..f02658c8ec --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/PublicRoleSelectMenu.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.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +/** Class representing a public-only role select (dropdown) menu. **/ +public open class PublicRoleSelectMenu( + timeoutTask: Task?, + public override val modal: (() -> M)? = null, +) : 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 new file mode 100644 index 0000000000..aed04315c8 --- /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..e100271562 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/role/RoleSelectMenu.kt @@ -0,0 +1,26 @@ +/* + * 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 +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu +import dev.kord.rest.builder.component.ActionRowBuilder + +/** Interface for role select menus. **/ +public interface RoleSelectMenu { + + /** 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(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 new file mode 100644 index 0000000000..a23d651666 --- /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.menus.SelectMenu +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: SelectMenu<*, *>, + 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..012dbce302 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/EphemeralStringSelectMenu.kt @@ -0,0 +1,38 @@ +/* + * 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.EphemeralSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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 new file mode 100644 index 0000000000..a533bc5883 --- /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..28083c12fb --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenu.kt @@ -0,0 +1,38 @@ +/* + * 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.PublicSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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/PublicSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/string/PublicStringSelectMenuContext.kt similarity index 70% 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..16a124d5e7 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 + 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 new file mode 100644 index 0000000000..d5babab887 --- /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.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 + +/** 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. **/ + public val options: MutableList + + /** Add an option to this select menu. **/ + @Suppress("UnnecessaryParentheses") + public 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) + } + + /** 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(selectMenu.id) { + this.allowedValues = selectMenu.minimumChoices..selectMenu.maximumChoices!! + ((this as? StringSelectBuilder)?.options ?: mutableListOf()).addAll(this@StringSelectMenu.options) + this.placeholder = selectMenu.placeholder + this.disabled = selectMenu.disabled + } + } + + /** Validate the options of the string select menu. **/ + @Suppress("UnnecessaryParentheses") + public fun validateOptions() { + 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.") + } + } +} 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..77c5b24b87 --- /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.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: SelectMenu<*, *>, + 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..b3a7384d69 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/EphemeralUserSelectMenu.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.user + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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 new file mode 100644 index 0000000000..eeaaca34ac --- /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..dbdb77699b --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/PublicUserSelectMenu.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.user + +import com.kotlindiscord.kord.extensions.components.forms.ModalForm +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +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 = + (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, +) : 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 new file mode 100644 index 0000000000..7f0bbab6ad --- /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..42333c981b --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/user/UserSelectMenu.kt @@ -0,0 +1,26 @@ +/* + * 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 +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu +import dev.kord.rest.builder.component.ActionRowBuilder + +/** Interface for user select menus. **/ +public interface UserSelectMenu { + + /** 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(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 new file mode 100644 index 0000000000..c8ed620294 --- /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.menus.SelectMenu +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: SelectMenu<*, *>, + 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 22cb9fc507..00dce80ed5 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 @@ -78,6 +78,7 @@ public suspend fun main() { add(::ModalTestExtension) add(::PaginatorTestExtension) add(::PKTestExtension) + add(::SelectorTestExtension) add(::NestingTestExtension) } 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..c69e1d0db6 --- /dev/null +++ b/test-bot/src/main/kotlin/com/kotlindiscord/kord/extensions/testbot/extensions/SelectorTestExtension.kt @@ -0,0 +1,152 @@ +/* + * 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.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 +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 + +public class SelectorTestExtension : Extension() { + override val name: String = "Select Menus Test" + + override suspend fun setup() { + publicSlashCommand { + name = "selector" + description = "Test selectors." + + publicSubCommand { + name = "public" + description = "Test public 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.map { it.asUser().username }.joinToString("\n") + } + } + } + + publicRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asRole().name }.joinToString("\n") + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.id.value }.joinToString("\n") + } + } + } + + publicChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.map { it.asChannelOf().name }.joinToString("\n") + } + } + } + } + } + } + } + + 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.map { it.asUser().username }.joinToString("\n") + } + } + } + + ephemeralRoleSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.asRole().name }.joinToString("\n") + } + } + } + + ephemeralChannelSelectMenu { + maximumChoices = null + + action { + respond { + content = selected.map { it.id.value }.joinToString("\n") + } + } + } + + ephemeralChannelSelectMenu { + maximumChoices = null + channelType(ChannelType.GuildText) + + action { + respond { + content = selected.map { it.asChannelOf().name }.joinToString("\n") + } + } + } + } + } + } + } + } + } +}