Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.freya022.bot.commands.slash

import dev.freya02.botcommands.jda.ktx.components.StringSelectMenu
import dev.freya02.botcommands.jda.ktx.components.TextInput
import dev.freya02.botcommands.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.annotations.Command
import io.github.freya022.botcommands.api.commands.application.ApplicationCommand
Expand All @@ -8,30 +10,43 @@ import io.github.freya022.botcommands.api.commands.application.slash.annotations
import io.github.freya022.botcommands.api.modals.Modals
import io.github.freya022.botcommands.api.modals.annotations.RequiresModals
import io.github.freya022.botcommands.api.modals.create
import io.github.freya022.botcommands.api.modals.paragraphTextInput
import net.dv8tion.jda.api.components.textinput.TextInputStyle

private const val codeInputName = "SlashModal: codeInput"
private const val codeInputId = "SlashModal: codeInput"
private const val languageInputId = "SlashModal: languageInput"

@Command
@RequiresModals
class SlashModal(private val modals: Modals) : ApplicationCommand() {
class SlashFormat(private val modals: Modals) : ApplicationCommand() {

@JDASlashCommand(name = "format", description = "Formats your code")
suspend fun onSlashFormat(event: GuildSlashEvent) {
val modal = modals.create("Format your code") {
paragraphTextInput(codeInputName, "Code") {
minLength = 3
label("Code") {
child = TextInput(codeInputId, TextInputStyle.PARAGRAPH) {
minLength = 3
}
}

label("Language") {
child = StringSelectMenu(languageInputId) {
option("Kotlin", "kt")
option("Java", "java")
}
}
}
event.replyModal(modal).queue()

val modalEvent = modal.await()
val code = modalEvent.values.first().asString
val code = modalEvent.values[0].asString
val language = modalEvent.values[1].asStringList[0]

modalEvent.reply_(
"""
modalEvent.reply_(ephemeral = true) {
content = """
Here is your formatted code:
```kt
```$language
$code```
""".trimIndent(), ephemeral = true).queue()
""".trimIndent()
}.queue()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import io.github.freya022.botcommands.api.components.ratelimit.ComponentRateLimi
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.internal.components.controller.ComponentController
import kotlinx.coroutines.runBlocking
import net.dv8tion.jda.api.components.ActionComponent
import net.dv8tion.jda.api.components.MessageTopLevelComponent
import net.dv8tion.jda.api.components.attribute.ICustomId
import net.dv8tion.jda.api.components.tree.ComponentTree
import javax.annotation.CheckReturnValue

Expand Down Expand Up @@ -77,7 +77,7 @@ abstract class AbstractComponentFactory internal constructor(
* and components from the same group will also be deleted according to the [timeout][ITimeoutableComponent.timeout] documentation.
*/
@JvmName("deleteJdaComponents")
fun deleteJdaComponentsJava(vararg components: ActionComponent) = deleteJdaComponentsJava(components.asList())
fun deleteJdaComponentsJava(vararg components: ICustomId) = deleteJdaComponentsJava(components.asList())

/**
* Removes the component data stored by the framework of the provided components.
Expand All @@ -86,7 +86,7 @@ abstract class AbstractComponentFactory internal constructor(
* and components from the same group will also be deleted according to the [timeout][ITimeoutableComponent.timeout] documentation.
*/
@JvmSynthetic
suspend fun deleteJdaComponents(vararg components: ActionComponent) = deleteJdaComponents(components.asList())
suspend fun deleteJdaComponents(vararg components: ICustomId) = deleteJdaComponents(components.asList())

/**
* Removes the component data stored by the framework of the provided components.
Expand All @@ -95,7 +95,7 @@ abstract class AbstractComponentFactory internal constructor(
* and components from the same group will also be deleted according to the [timeout][ITimeoutableComponent.timeout] documentation.
*/
@JvmName("deleteJdaComponents")
fun deleteJdaComponentsJava(components: Collection<ActionComponent>) = runBlocking { deleteJdaComponents(components) }
fun deleteJdaComponentsJava(components: Collection<ICustomId>) = runBlocking { deleteJdaComponents(components) }

/**
* Removes the component data stored by the framework of the provided components.
Expand All @@ -104,7 +104,7 @@ abstract class AbstractComponentFactory internal constructor(
* and components from the same group will also be deleted according to the [timeout][ITimeoutableComponent.timeout] documentation.
*/
@JvmSynthetic
suspend fun deleteJdaComponents(components: Collection<ActionComponent>) =
suspend fun deleteJdaComponents(components: Collection<ICustomId>) =
components
.mapNotNull { it.customId }
.mapNotNull { IdentifiableComponent.fromIdOrNull(it) }
Expand Down Expand Up @@ -135,15 +135,15 @@ abstract class AbstractComponentFactory internal constructor(
@JvmSynthetic
@JvmName("deleteRows")
suspend fun deleteTreeJava(tree: ComponentTree<*>) =
tree.findAll<ActionComponent>()
tree.findAll<ICustomId>()
.mapNotNull { it.customId }
.mapNotNull { IdentifiableComponent.fromIdOrNull(it) }
.let { deleteComponents(it) }


@JvmSynthetic
suspend fun deleteTree(tree: ComponentTree<*>) =
tree.findAll<ActionComponent>()
tree.findAll<ICustomId>()
.mapNotNull { it.customId }
.mapNotNull { IdentifiableComponent.fromIdOrNull(it) }
.let { deleteComponents(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.freya022.botcommands.api.components

import io.github.freya022.botcommands.internal.components.controller.ComponentController
import io.github.freya022.botcommands.internal.utils.throwArgument
import net.dv8tion.jda.api.components.ActionComponent
import net.dv8tion.jda.api.components.attribute.ICustomId

interface IdentifiableComponent {
val internalId: Int
Expand All @@ -12,16 +12,16 @@ interface IdentifiableComponent {
fun isCompatible(id: String): Boolean = ComponentController.isCompatibleComponent(id)

@JvmSynthetic
fun ActionComponent.toIdentifiableComponent(): IdentifiableComponent = fromComponent(this)
fun ICustomId.toIdentifiableComponent(): IdentifiableComponent = fromComponent(this)
@JvmSynthetic
fun ActionComponent.toIdentifiableComponentOrNull(): IdentifiableComponent? = fromComponentOrNull(this)
fun ICustomId.toIdentifiableComponentOrNull(): IdentifiableComponent? = fromComponentOrNull(this)

@JvmStatic
fun fromComponent(component: ActionComponent): IdentifiableComponent =
fun fromComponent(component: ICustomId): IdentifiableComponent =
fromId(component.customId ?: throwArgument("This component has no ID"))

@JvmStatic
fun fromComponentOrNull(component: ActionComponent): IdentifiableComponent? =
fun fromComponentOrNull(component: ICustomId): IdentifiableComponent? =
fromIdOrNull(component.customId ?: throwArgument("This component has no ID"))

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package io.github.freya022.botcommands.api.modals

import dev.freya02.botcommands.jda.ktx.components.InlineLabel
import dev.freya02.botcommands.jda.ktx.components.Label
import io.github.freya022.botcommands.api.modals.Modal as BCModal
import io.github.freya022.botcommands.api.modals.annotations.ModalData
import io.github.freya022.botcommands.api.modals.annotations.ModalHandler
import io.github.freya022.botcommands.api.modals.annotations.ModalInput
import io.github.freya022.botcommands.internal.modals.ModalDSL
import net.dv8tion.jda.api.components.Component
import net.dv8tion.jda.api.components.ModalTopLevelComponent
import net.dv8tion.jda.api.components.label.Label
import net.dv8tion.jda.api.components.label.LabelChildComponent
import net.dv8tion.jda.api.components.tree.ComponentTree
import net.dv8tion.jda.api.modals.Modal
import net.dv8tion.jda.api.modals.Modal as JDAModal
import java.time.Duration as JavaDuration
import java.util.concurrent.TimeUnit
Expand All @@ -22,6 +32,9 @@ abstract class ModalBuilder protected constructor(
/**
* Binds the action to a [@ModalHandler][ModalHandler] with its arguments.
*
* Each [@ModalInput][ModalInput] must match a component's custom ID,
* alternatively, you can always retrieve input values from the event.
*
* @param handlerName The name of the modal handler, which must be the same as your [@ModalHandler][ModalHandler]
* @param userData The optional user data to be passed to the modal handler via [@ModalData][ModalData]
*
Expand All @@ -33,6 +46,9 @@ abstract class ModalBuilder protected constructor(
/**
* Binds the action to a [@ModalHandler][ModalHandler] with its arguments.
*
* Each [@ModalInput][ModalInput] must match a component's custom ID,
* alternatively, you can always retrieve input values from the event.
*
* @param handlerName The name of the modal handler, which must be the same as your [@ModalHandler][ModalHandler]
* @param userData The optional user data to be passed to the modal handler via [@ModalData][ModalData]
*
Expand Down Expand Up @@ -114,6 +130,20 @@ abstract class ModalBuilder protected constructor(
@JvmSynthetic
abstract fun timeout(timeout: Duration, onTimeout: (suspend () -> Unit)? = null): ModalBuilder

override fun setTitle(title: String): ModalBuilder = apply { super.setTitle(title) }

override fun addComponents(components: Collection<ModalTopLevelComponent>): ModalBuilder = apply {
super.addComponents(components)
}

override fun addComponents(vararg components: ModalTopLevelComponent): ModalBuilder = apply {
super.addComponents(*components)
}

override fun addComponents(tree: ComponentTree<out ModalTopLevelComponent>): ModalBuilder = apply {
super.addComponents(tree)
}

@Deprecated("Cannot set an ID on modals managed by the framework", level = DeprecationLevel.ERROR)
abstract override fun setId(customId: String): ModalBuilder

Expand All @@ -126,5 +156,97 @@ abstract class ModalBuilder protected constructor(
}

@CheckReturnValue
abstract override fun build(): Modal
abstract override fun build(): BCModal
}

@ModalDSL
class InlineModal(val builder: ModalBuilder) {

val components: MutableList<ModalTopLevelComponent> = arrayListOf()

operator fun ModalTopLevelComponent.unaryPlus() {
components += this
}

operator fun Collection<ModalTopLevelComponent>.unaryPlus() {
components += this
}

/** Title of this Modal, see [Modal.Builder.setTitle] */
var title: String
get() = builder.title
set(value) {
builder.title = value
}

/**
* Component that contains a label, an optional description,
* and a [child component][LabelChildComponent], see [Label][net.dv8tion.jda.api.components.label.Label].
*
* @param label Label of the Label, see [Label.withLabel]
* @param uniqueId Unique identifier of this component, see [Component.withUniqueId]
* @param description The description of this Label, see [Label.withDescription]
* @param child The child contained by this Label, see [Label.withChild]
* @param block Lambda allowing further configuration
*/
inline fun label(
label: String?,
uniqueId: Int = -1,
description: String? = null,
child: LabelChildComponent? = null,
block: InlineLabel.() -> Unit = {},
) {
builder.addComponents(Label(label, uniqueId, description, child, block))
}

/**
* Binds the action to a [@ModalHandler][ModalHandler] with its arguments.
*
* Each [@ModalInput][ModalInput] must match a component's custom ID,
* alternatively, you can always retrieve input values from the event.
*
* @param handlerName The name of the modal handler, which must be the same as your [@ModalHandler][ModalHandler]
* @param userData The optional user data to be passed to the modal handler via [@ModalData][ModalData]
*/
fun bindTo(handlerName: String, userData: List<Any?>) {
builder.bindTo(handlerName, userData)
}

/**
* Binds the action to a [@ModalHandler][ModalHandler] with its arguments.
*
* Each [@ModalInput][ModalInput] must match a component's custom ID,
* alternatively, you can always retrieve input values from the event.
*
* @param handlerName The name of the modal handler, which must be the same as your [@ModalHandler][ModalHandler]
* @param userData The optional user data to be passed to the modal handler via [@ModalData][ModalData]
*/
fun bindTo(handlerName: String, vararg userData: Any?) = builder.bindTo(handlerName, *userData)

/**
* Binds the action to the closure.
*
* @param handler The modal handler to run when the modal is used
*/
fun bindTo(handler: suspend (ModalEvent) -> Unit) {
builder.bindTo(handler)
}

/**
* Sets the timeout for this modal, invalidating the modal after expiration,
* and running the given timeout handler.
*
* If unset, the timeout is set to [Modals.defaultTimeout].
*
* @param timeout The amount of time before the modal is removed
* @param onTimeout The function to run when the timeout has been reached
*/
@JvmSynthetic // Mute Java Duration test
fun timeout(timeout: Duration, onTimeout: (suspend () -> Unit)? = null) {
builder.timeout(timeout, onTimeout)
}

fun build(): BCModal {
return builder.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.github.freya022.botcommands.internal.localization.interaction.Localiza
import io.github.freya022.botcommands.internal.localization.interaction.LocalizableInteractionImpl
import io.github.freya022.botcommands.internal.localization.interaction.LocalizableReplyCallbackImpl
import io.github.freya022.botcommands.internal.utils.throwArgument
import net.dv8tion.jda.api.components.ActionComponent
import net.dv8tion.jda.api.components.attribute.ICustomId
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent
import net.dv8tion.jda.api.interactions.modals.ModalMapping
import java.util.*
Expand Down Expand Up @@ -42,7 +42,7 @@ class ModalEvent internal constructor(
override fun getRawData() = event.rawData

@JvmName("getValue")
operator fun get(component: ActionComponent): ModalMapping {
operator fun get(component: ICustomId): ModalMapping {
require(component.isModalCompatible) {
"Can only get modal mapping for modal-compatible components, provided: $component"
}
Expand Down
Loading