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

io.github.freya022.botcommands.internal.commands.text.TextCommandVariationImpl.kt Maven / Gradle / Ivy

Go to download

A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.

There is a newer version: 3.0.0-alpha.18
Show newest version
package io.github.freya022.botcommands.internal.commands.text

import io.github.freya022.botcommands.api.commands.ratelimit.CancellableRateLimit
import io.github.freya022.botcommands.api.commands.text.*
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.DeclarationSite
import io.github.freya022.botcommands.api.core.Filter
import io.github.freya022.botcommands.api.core.utils.isSubclassOf
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import io.github.freya022.botcommands.api.localization.text.LocalizableTextCommand
import io.github.freya022.botcommands.internal.ExecutableMixin
import io.github.freya022.botcommands.internal.commands.application.slash.SlashUtils.getCheckedDefaultValue
import io.github.freya022.botcommands.internal.commands.text.builder.TextCommandVariationBuilderImpl
import io.github.freya022.botcommands.internal.commands.text.options.TextCommandOptionImpl
import io.github.freya022.botcommands.internal.commands.text.options.TextCommandParameterImpl
import io.github.freya022.botcommands.internal.commands.text.options.TextGeneratedOption
import io.github.freya022.botcommands.internal.commands.text.options.builder.TextCommandOptionAggregateBuilderImpl
import io.github.freya022.botcommands.internal.core.options.OptionImpl
import io.github.freya022.botcommands.internal.core.options.OptionType
import io.github.freya022.botcommands.internal.core.reflection.toMemberParamFunction
import io.github.freya022.botcommands.internal.options.transform
import io.github.freya022.botcommands.internal.parameters.CustomMethodOption
import io.github.freya022.botcommands.internal.parameters.ServiceMethodOption
import io.github.freya022.botcommands.internal.utils.*
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import kotlin.reflect.full.callSuspendBy
import kotlin.reflect.jvm.jvmErasure

internal class TextCommandVariationImpl internal constructor(
    override val context: BContext,
    override val command: TextCommandInfo,
    builder: TextCommandVariationBuilderImpl
) : TextCommandVariation,
    ExecutableMixin {

    override val declarationSite: DeclarationSite = builder.declarationSite
    override val eventFunction = builder.toMemberParamFunction(context)
    override val parameters: List

    /**
     * Set of filters preventing this command from executing.
     *
     * @see TextCommandFilter
     * @see TextCommandRejectionHandler
     */
    val filters: List> = builder.filters.onEach { filter ->
        require(!filter.global) {
            "Global filter ${filter.javaClass.simpleNestedName} cannot be used explicitly, see ${Filter::global.reference}"
        }
    }

    override fun hasFilters(): Boolean = filters.isNotEmpty()

    override val description: String? = builder.description
    override val usage: String? = builder.usage
    override val example: String? = builder.example

    override val completePattern: Regex?

    private val useTokenizedEvent: Boolean

    init {
        useTokenizedEvent = eventFunction.firstParameter.type.jvmErasure.isSubclassOf()

        parameters = builder.optionAggregateBuilders.transform {
            TextCommandParameterImpl(context, this, it as TextCommandOptionAggregateBuilderImpl)
        }

        completePattern = when {
            parameters.flatMap { it.allOptions }.any { it.optionType == OptionType.OPTION } -> CommandPattern.of(this)
            else -> null
        }
    }

    internal suspend fun createEvent(jdaEvent: MessageReceivedEvent, args: String, cancellableRateLimit: CancellableRateLimit, localizableTextCommand: LocalizableTextCommand): BaseCommandEvent = when {
        useTokenizedEvent -> CommandEventImpl.create(context, jdaEvent, args, cancellableRateLimit, localizableTextCommand)
        else -> BaseCommandEventImpl(context, jdaEvent, args, cancellableRateLimit, localizableTextCommand)
    }

    internal suspend fun tryParseOptionValues(event: BaseCommandEvent, matchResult: MatchResult?): Map? {
        val groupsIterator = matchResult?.groups?.iterator()
        groupsIterator?.next() //Skip the entire match

        return parameters.mapOptions { option ->
            if (tryInsertOption(event, this, option, groupsIterator) == InsertOptionResult.ABORT)
                return null
        }
    }

    internal suspend fun execute(event: BaseCommandEvent, optionValues: Map) {
        val finalParameters = parameters.mapFinalParameters(event, optionValues)

        function.callSuspendBy(finalParameters)
    }

    /**
     * Will return a null value if it can go to the next option
     *
     * A non-null value is returned immediately to #insertAggregate caller
     */
    private suspend fun tryInsertOption(
        event: BaseCommandEvent,
        optionMap: MutableMap,
        option: OptionImpl,
        groupsIterator: Iterator?
    ): InsertOptionResult {
        val value = when (option.optionType) {
            OptionType.OPTION -> {
                groupsIterator ?: throwInternal("No group iterator passed for a regex-based text command")

                option as TextCommandOptionImpl

                val groups: Array = Array(option.groupCount) { groupsIterator.next()?.value }
                option.resolver.resolveSuspend(option, event, groups)
            }

            OptionType.CUSTOM -> {
                option as CustomMethodOption

                option.resolver.resolveSuspend(option, event)
            }

            OptionType.GENERATED -> {
                option as TextGeneratedOption

                option.getCheckedDefaultValue { it.generatedValueSupplier.getDefaultValue(event) }
            }

            OptionType.SERVICE -> (option as ServiceMethodOption).getService()

            OptionType.CONSTANT -> throwInternal("${option.optionType} has not been implemented")
        }

        // If value is null and required, go to next variation
        if (value == null && !option.isOptionalOrNullable)
            return InsertOptionResult.ABORT

        return tryInsertNullableOption(value, option, optionMap)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy