All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.freya02.botcommands.api.components.Components.kt Maven / Gradle / Ivy

package com.freya02.botcommands.api.components

import com.freya02.botcommands.api.BContext
import com.freya02.botcommands.api.ReceiverConsumer
import com.freya02.botcommands.api.apply
import com.freya02.botcommands.api.components.builder.button.EphemeralButtonBuilder
import com.freya02.botcommands.api.components.builder.button.PersistentButtonBuilder
import com.freya02.botcommands.api.components.builder.group.ComponentGroupBuilder
import com.freya02.botcommands.api.components.builder.group.EphemeralComponentGroupBuilder
import com.freya02.botcommands.api.components.builder.group.PersistentComponentGroupBuilder
import com.freya02.botcommands.api.components.builder.select.ephemeral.EphemeralEntitySelectBuilder
import com.freya02.botcommands.api.components.builder.select.ephemeral.EphemeralStringSelectBuilder
import com.freya02.botcommands.api.components.builder.select.persistent.PersistentEntitySelectBuilder
import com.freya02.botcommands.api.components.builder.select.persistent.PersistentStringSelectBuilder
import com.freya02.botcommands.api.core.config.BComponentsConfig
import com.freya02.botcommands.api.core.service.ConditionalServiceChecker
import com.freya02.botcommands.api.core.service.annotations.BService
import com.freya02.botcommands.api.core.service.annotations.ConditionalService
import com.freya02.botcommands.api.utils.ButtonContent
import com.freya02.botcommands.internal.components.controller.ComponentController
import com.freya02.botcommands.internal.requireUser
import com.freya02.botcommands.internal.throwUser
import com.freya02.botcommands.internal.utils.ReflectionUtils.referenceString
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import net.dv8tion.jda.api.entities.emoji.Emoji
import net.dv8tion.jda.api.interactions.components.ActionComponent
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle
import net.dv8tion.jda.api.interactions.components.selections.EntitySelectMenu.SelectTarget

/**
 * This class lets you create smart components such as buttons, select menus and groups.
 *
 * Every component can either be persistent or ephemeral, all components can be configured to:
 *  - Be used once
 *  - Have timeouts
 *  - Have handlers
 *  - Have constraints (checks before the button can be used)
 *
 * Except component groups which can only have its timeout configured.
 *
 * **Persistent components**:
 *  - Kept after restart
 *  - Handlers are methods, they can have arguments passed to them
 *  - Timeouts are also methods, additionally they will be rescheduled when the bot restarts
 *
 * **Ephemeral components**:
 *  - Are deleted once the bot restarts
 *  - Handlers are closures, they can capture objects, but you [shouldn't capture JDA entities](https://jda.wiki/using-jda/troubleshooting/#cannot-get-reference-as-it-has-already-been-garbage-collected)
 *  - Timeouts are also closures, but are not rescheduled when restarting
 *
 * **Component groups**:
 *  - If deleted, all contained components are deleted
 *  - If one of the contained components is deleted, then all of its subsequent groups are also deleted
 *
 * **Example**:
 * ```java
 * public class SlashButton extends ApplicationCommand {
 * 	private static final String PERSISTENT_BUTTON_LISTENER_NAME = "leBouton";
 *
 * 	@JDASlashCommand(name = "button")
 * 	public void onSlashButton(GuildSlashEvent event, Components components) {
 * 		event.reply("Buttons")
 * 				.addActionRow(
 * 						components.persistentButton(ButtonStyle.PRIMARY, "Persistent button (1 minute timeout)", builder -> {
 * 							builder.setOneUse(true);
 * 							builder.bindTo(PERSISTENT_BUTTON_LISTENER_NAME, System.currentTimeMillis());
 * 							builder.timeout(1, TimeUnit.MINUTES);
 * 						}),
 * 						components.ephemeralButton(ButtonStyle.PRIMARY, "Ephemeral button (1 second timeout)", builder -> {
 * 							builder.bindTo(btnEvt -> btnEvt.deferEdit().queue());
 * 							builder.timeout(1, TimeUnit.SECONDS, () -> event.getHook().editOriginal("Ephemeral expired :/").queue());
 * 						})
 * 				)
 * 				.setEphemeral(true)
 * 				.queue();
 * 	}
 *
 * 	@JDAButtonListener(name = PERSISTENT_BUTTON_LISTENER_NAME)
 * 	public void onPersistentButtonClicked(ButtonEvent event, @AppOption long timeCreated, JDA jda) {
 * 		event.replyFormat("Button created on %s and I am %s", timeCreated, jda.getSelfUser().getAsTag())
 * 				.setEphemeral(true)
 * 				.queue();
 * 	}
 * }
 * ```
 */
@BService
@ConditionalService(Components.Companion::class)
class Components internal constructor(private val componentController: ComponentController) {
    private val logger = KotlinLogging.logger { }

    // -------------------- Persistent groups --------------------

    fun newPersistentGroup(block: PersistentComponentGroupBuilder.() -> Unit, vararg components: ActionComponent): ComponentGroup = runBlocking {
        createGroup({ PersistentComponentGroupBuilder(it).apply(block) }, *components)
    }

    @JvmSynthetic
    suspend fun newPersistentGroup(vararg components: ActionComponent, block: PersistentComponentGroupBuilder.() -> Unit): ComponentGroup =
        createGroup({ PersistentComponentGroupBuilder(it).apply(block) }, *components)

    // -------------------- Ephemeral groups --------------------

    fun newEphemeralGroup(block: EphemeralComponentGroupBuilder.() -> Unit, vararg components: ActionComponent): ComponentGroup = runBlocking {
        createGroup({ EphemeralComponentGroupBuilder(it).apply(block) }, *components)
    }

    @JvmSynthetic
    suspend fun newEphemeralGroup(vararg components: ActionComponent, block: EphemeralComponentGroupBuilder.() -> Unit): ComponentGroup =
        createGroup({ EphemeralComponentGroupBuilder(it).apply(block) }, *components)

    // -------------------- Persistent buttons --------------------

    /** See [Button.of][net.dv8tion.jda.api.interactions.components.buttons.Button.of] */
    @JvmOverloads
    fun persistentButton(style: ButtonStyle, label: String? = null, emoji: Emoji? = null, block: ReceiverConsumer) =
        PersistentButtonBuilder(style, componentController).apply(block).build(label, emoji)
    /** See [Button.of][net.dv8tion.jda.api.interactions.components.buttons.Button.of] */
    fun persistentButton(style: ButtonStyle, content: ButtonContent, block: ReceiverConsumer) =
        persistentButton(style, content.text, content.emoji, block)

    // -------------------- Ephemeral buttons --------------------

    /** See [Button.of][net.dv8tion.jda.api.interactions.components.buttons.Button.of] */
    @JvmOverloads
    fun ephemeralButton(style: ButtonStyle, label: String? = null, emoji: Emoji? = null, block: ReceiverConsumer) =
        EphemeralButtonBuilder(style, componentController).apply(block).build(label, emoji)
    /** See [Button.of][net.dv8tion.jda.api.interactions.components.buttons.Button.of] */
    fun ephemeralButton(style: ButtonStyle, content: ButtonContent, block: ReceiverConsumer) =
        ephemeralButton(style, content.text, content.emoji, block)

    // -------------------- Persistent select menus --------------------

    fun persistentStringSelectMenu(block: ReceiverConsumer) =
        PersistentStringSelectBuilder(componentController).apply(block).doBuild()
    fun persistentEntitySelectMenu(target: SelectTarget, block: ReceiverConsumer) =
        persistentEntitySelectMenu(listOf(target), block)
    fun persistentEntitySelectMenu(targets: List, block: ReceiverConsumer) =
        PersistentEntitySelectBuilder(componentController, targets).apply(block).doBuild()

    // -------------------- Ephemeral select menus --------------------

    fun ephemeralStringSelectMenu(block: ReceiverConsumer) =
        EphemeralStringSelectBuilder(componentController).apply(block).doBuild()
    fun ephemeralEntitySelectMenu(target: SelectTarget, block: ReceiverConsumer) =
        ephemeralEntitySelectMenu(listOf(target), block)
    fun ephemeralEntitySelectMenu(targets: List, block: ReceiverConsumer) =
        EphemeralEntitySelectBuilder(componentController, targets).apply(block).doBuild()

    @JvmName("deleteComponentsById")
    fun deleteComponentsByIdJava(ids: Collection) = runBlocking { deleteComponentsById(ids) }

    @JvmSynthetic
    suspend fun deleteComponentsById(ids: Collection) {
        componentController.deleteComponentsById(ids.mapNotNull { it.toIntOrNull() })
    }

    private suspend fun createGroup(factory: (List) -> ComponentGroupBuilder, vararg components: ActionComponent): ComponentGroup {
        requireUser(components.none { it.id == null }) {
            "Cannot make groups with link buttons"
        }

        return components
            .map { it.id?.toIntOrNull() ?: throwUser("Cannot put external components in groups") }
            .let { componentIds -> factory(componentIds) }
            .let { componentController.insertGroup(it) }
    }

    internal companion object : ConditionalServiceChecker {
        override fun checkServiceAvailability(context: BContext): String? {
            if (context.componentsConfig.useComponents) {
                return null
            }

            return "Components needs to be enabled, see ${BComponentsConfig::useComponents.referenceString}"
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy