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

io.github.freya022.botcommands.internal.commands.application.ApplicationCommandsContextImpl.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.application

import gnu.trove.map.hash.TLongObjectHashMap
import io.github.freya022.botcommands.api.commands.CommandPath
import io.github.freya022.botcommands.api.commands.application.*
import io.github.freya022.botcommands.api.commands.application.annotations.RequiresApplicationCommands
import io.github.freya022.botcommands.api.commands.application.context.message.MessageCommandInfo
import io.github.freya022.botcommands.api.commands.application.context.user.UserCommandInfo
import io.github.freya022.botcommands.api.commands.application.slash.SlashCommandInfo
import io.github.freya022.botcommands.api.commands.application.slash.TopLevelSlashCommandInfo
import io.github.freya022.botcommands.api.core.config.BCoroutineScopesConfig
import io.github.freya022.botcommands.api.core.debugNull
import io.github.freya022.botcommands.api.core.service.ServiceContainer
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.service.lazy
import io.github.freya022.botcommands.api.core.utils.loggerOf
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import io.github.freya022.botcommands.api.core.utils.unmodifiableView
import io.github.freya022.botcommands.internal.commands.application.slash.autocomplete.AutocompleteInfoContainer
import io.github.freya022.botcommands.internal.utils.classRef
import io.github.freya022.botcommands.internal.utils.safeCast
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import net.dv8tion.jda.api.entities.Guild
import java.util.concurrent.CompletableFuture
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.reflect.KFunction

private val logger = KotlinLogging.loggerOf()

@BService
@RequiresApplicationCommands
internal class ApplicationCommandsContextImpl internal constructor(
    private val coroutineScopesConfig: BCoroutineScopesConfig,
    private val autocompleteInfoContainer: AutocompleteInfoContainer,
    serviceContainer: ServiceContainer
) : ApplicationCommandsContext {
    private val applicationCommandsBuilder: ApplicationCommandsBuilder by serviceContainer.lazy()

    private val writeLock = ReentrantLock()
    private val liveTopLevelApplicationCommands = TLongObjectHashMap()

    override fun findSlashCommand(guild: Guild?, path: CommandPath): SlashCommandInfo? {
        val topLevelCommand = liveTopLevelApplicationCommands.valueCollection()
            .find { it.guildId == guild?.idLong && it.name == path.name }
            ?: return logger.debugNull { "Could not find slash command with top-level name '${path.name}'" }

        return getApplicationCommandById(topLevelCommand.idLong, path.group, path.subname)
    }

    override fun findUserCommand(guild: Guild?, name: String): UserCommandInfo? {
        val topLevelCommand = liveTopLevelApplicationCommands.valueCollection()
            .find { it.guildId == guild?.idLong && it.name == name }
            ?: return logger.debugNull { "Could not find user command '$name'" }

        return topLevelCommand as? UserCommandInfo
            ?: return logger.debugNull { "Top level command '$name' is not a ${classRef()}" }
    }

    override fun findMessageCommand(guild: Guild?, name: String): MessageCommandInfo? {
        val topLevelCommand = liveTopLevelApplicationCommands.valueCollection()
            .find { it.guildId == guild?.idLong && it.name == name }
            ?: return logger.debugNull { "Could not find message command '$name'" }

        return topLevelCommand as? MessageCommandInfo
            ?: return logger.debugNull { "Top level command '$name' is not a ${classRef()}" }
    }

    override fun getApplicationCommands(guild: Guild?): List =
        liveTopLevelApplicationCommands.valueCollection().filter { it.guildId == guild?.idLong }

    override fun getEffectiveApplicationCommands(guild: Guild?): List {
        val topLevelCommands = liveTopLevelApplicationCommands.valueCollection()
        return when (guild) {
            // Keep global
            null -> topLevelCommands.filter { it.guildId == null }.unmodifiableView()
            // Keep global and guild with id
            else -> topLevelCommands.filter { it.guildId == null || it.guildId == guild.idLong }.unmodifiableView()
        }
    }

    internal fun putApplicationCommands(topLevelCommands: Collection): Unit = writeLock.withLock {
        topLevelCommands.forEach { topLevelCommand ->
            liveTopLevelApplicationCommands.put(topLevelCommand.idLong, topLevelCommand)
        }
    }

    // Logged at debug as this could be used for introspection
    override fun  getApplicationCommandById(type: Class, commandId: Long, group: String?, subcommand: String?): T? {
        val topLevelCommand = liveTopLevelApplicationCommands[commandId]
            ?: return logger.debugNull { "Could not find command with id $commandId" }

        var command: ApplicationCommandInfo = topLevelCommand
        if (topLevelCommand is TopLevelSlashCommandInfo) {
            if (group != null) {
                requireNotNull(subcommand) { "Subcommand cannot be null if a group is specified" }
                val subgroup = topLevelCommand.subcommandGroups[group]
                    ?: return logger.debugNull { "Could not find group '$group' in '${topLevelCommand.name}' ($commandId)" }
                command = subgroup.subcommands[subcommand]
                    ?: return logger.debugNull { "Could not find subcommand '$subcommand' in '${topLevelCommand.name} $group' ($commandId)" }
            } else if (subcommand != null) {
                command = topLevelCommand.subcommands[subcommand]
                    ?: return logger.debugNull { "Could not find subcommand '$subcommand' in '${topLevelCommand.name}' ($commandId)" }
            }
        }

        return type.safeCast(command)
            ?: return logger.debugNull { "Command '${command.fullCommandName}' is not a ${type.simpleNestedName}" }
    }

    override fun updateGlobalApplicationCommands(force: Boolean): CompletableFuture {
        return coroutineScopesConfig.commandUpdateScope.async {
            applicationCommandsBuilder.updateGlobalCommands(force)
        }.asCompletableFuture()
    }

    override fun updateGuildApplicationCommands(guild: Guild, force: Boolean): CompletableFuture {
        return coroutineScopesConfig.commandUpdateScope.async {
            applicationCommandsBuilder.updateGuildCommands(guild, force)
        }.asCompletableFuture()
    }

    override fun invalidateAutocompleteCache(autocompleteHandlerName: String) {
        autocompleteInfoContainer[autocompleteHandlerName]?.invalidate()
    }

    override fun invalidateAutocompleteCache(autocompleteHandler: KFunction>) {
        autocompleteInfoContainer[autocompleteHandler]?.invalidate()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy