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
@@ -0,0 +1,139 @@
package io.github.freya022.botcommands.internal.commands.application.autobuilder

import io.github.freya022.botcommands.api.commands.application.ApplicationCommandFilter
import io.github.freya022.botcommands.api.commands.application.CommandScope
import io.github.freya022.botcommands.api.commands.application.annotations.DeclarationFilter
import io.github.freya022.botcommands.api.commands.application.annotations.Test
import io.github.freya022.botcommands.api.commands.application.builder.ApplicationCommandBuilder
import io.github.freya022.botcommands.api.commands.application.provider.*
import io.github.freya022.botcommands.api.commands.text.annotations.NSFW
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
import io.github.freya022.botcommands.api.core.objectLogger
import io.github.freya022.botcommands.api.core.utils.findAllAnnotations
import io.github.freya022.botcommands.api.core.utils.hasAnnotationRecursive
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import io.github.freya022.botcommands.internal.commands.SkipLogger
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.ApplicationFunctionMetadata
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.RootAnnotatedApplicationCommand
import io.github.freya022.botcommands.internal.commands.autobuilder.CommandAutoBuilder
import io.github.freya022.botcommands.internal.commands.autobuilder.forEachWithDelayedExceptions
import io.github.freya022.botcommands.internal.utils.*
import net.dv8tion.jda.api.interactions.commands.Command as JDACommand
import kotlin.reflect.KFunction

internal abstract class ApplicationCommandAutoBuilder<T : RootAnnotatedApplicationCommand>(
applicationConfig: BApplicationConfig
) : CommandAutoBuilder(),
GlobalApplicationCommandProvider,
GuildApplicationCommandProvider {

private val logger = this.objectLogger()

protected val forceGuildCommands: Boolean = applicationConfig.forceGuildCommands
protected abstract val commandType: JDACommand.Type

protected abstract val rootAnnotatedCommands: Collection<T>

override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
// On global manager, do not register any command if forceGuildCommands is enabled,
// as none of them would be global
if (forceGuildCommands)
return

val skipLogger = SkipLogger(logger)
rootAnnotatedCommands.forEachWithDelayedExceptions forEach@{ rootAnnotatedCommand ->
val scope = rootAnnotatedCommand.scope
if (scope != CommandScope.GLOBAL) return@forEach

val testState = checkTestCommand(manager, rootAnnotatedCommand.func, scope, manager.context)
if (testState != TestState.NO_ANNOTATION)
throwInternal("Test commands on a global scope should have thrown in ${::checkTestCommand.shortSignatureNoSrc}")

context(skipLogger) { declareTopLevel(manager, rootAnnotatedCommand) }
}

skipLogger.log(guild = null, commandType)
}

override fun declareGuildApplicationCommands(manager: GuildApplicationCommandManager) {
val skipLogger = SkipLogger(logger)
rootAnnotatedCommands.forEachWithDelayedExceptions forEach@{ rootAnnotatedCommand ->
val scope = rootAnnotatedCommand.scope

// If guild commands aren't forced, check the scope
val canBeDeclared = forceGuildCommands || scope == CommandScope.GUILD
if (!canBeDeclared) return@forEach

val testState = checkTestCommand(manager, rootAnnotatedCommand.func, scope, manager.context)
if (scope == CommandScope.GLOBAL && testState != TestState.NO_ANNOTATION)
throwInternal("Test commands on a global scope should have thrown in ${::checkTestCommand.shortSignatureNoSrc}")

if (testState == TestState.EXCLUDE)
return@forEach skipLogger.skip(rootAnnotatedCommand.name, "Is a test command while this guild isn't a test guild")

context(skipLogger) { declareTopLevel(manager, rootAnnotatedCommand) }
}

skipLogger.log(manager.guild, commandType)
}

private fun checkTestCommand(manager: AbstractApplicationCommandManager, func: KFunction<*>, scope: CommandScope, context: BContext): TestState {
if (func.hasAnnotationRecursive<Test>()) {
requireAt(scope == CommandScope.GUILD, func) {
"Test commands must have their scope set to GUILD"
}
if (manager !is GuildApplicationCommandManager) throwInternal("GUILD scoped command was not registered with a guild command manager")

//Returns whether the command can be registered
return when (manager.guild.idLong) {
in AnnotationUtils.getEffectiveTestGuildIds(context, func) -> TestState.INCLUDE
else -> TestState.EXCLUDE
}
}

return TestState.NO_ANNOTATION
}

context(logger: SkipLogger)
protected abstract fun declareTopLevel(manager: AbstractApplicationCommandManager, rootCommand: T)

context(logger: SkipLogger)
internal fun checkDeclarationFilter(
manager: AbstractApplicationCommandManager,
metadata: ApplicationFunctionMetadata<*>,
): Boolean {
val func = metadata.func
val path = metadata.path
val commandId = metadata.commandId

func.findAllAnnotations<DeclarationFilter>().forEach { declarationFilter ->
checkAt(manager is GuildApplicationCommandManager, func) {
"${annotationRef<DeclarationFilter>()} can only be used on guild commands"
}

declarationFilter.filters.forEach {
if (!serviceContainer.getService(it).filter(manager.guild, path, commandId)) {
val commandIdStr = if (commandId != null) " (id ${commandId})" else ""
logger.skip(path, "${it.simpleNestedName} rejected this command$commandIdStr")
return false
}
}
}
return true
}

protected fun ApplicationCommandBuilder<*>.fillApplicationCommandBuilder(func: KFunction<*>) {
filters += AnnotationUtils.getFilters(context, func, ApplicationCommandFilter::class)

if (func.hasAnnotationRecursive<NSFW>()) {
throwArgument(func, "${annotationRef<NSFW>()} can only be used on text commands, use the #nsfw method on your annotation instead")
}
}

private enum class TestState {
INCLUDE,
EXCLUDE,
NO_ANNOTATION
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ import io.github.freya022.botcommands.api.commands.application.context.annotatio
import io.github.freya022.botcommands.api.commands.application.context.message.options.builder.MessageCommandOptionRegistry
import io.github.freya022.botcommands.api.commands.application.context.user.options.builder.UserCommandOptionRegistry
import io.github.freya022.botcommands.api.commands.application.options.builder.ApplicationOptionRegistry
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandProvider
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandProvider
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
import io.github.freya022.botcommands.api.core.options.builder.inlineClassAggregate
import io.github.freya022.botcommands.api.core.reflect.wrap
import io.github.freya022.botcommands.api.core.service.ServiceContainer
import io.github.freya022.botcommands.api.parameters.resolvers.ICustomResolver
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.RootAnnotatedApplicationCommand
import io.github.freya022.botcommands.internal.commands.application.autobuilder.utils.ParameterAdapter
import io.github.freya022.botcommands.internal.commands.autobuilder.CommandAutoBuilder
import io.github.freya022.botcommands.internal.commands.autobuilder.requireServiceOptionOrOptional
import io.github.freya022.botcommands.internal.parameters.ResolverContainer
import io.github.freya022.botcommands.internal.utils.ReflectionUtils.nonInstanceParameters
import io.github.freya022.botcommands.internal.utils.findDeclarationName
Expand All @@ -26,17 +23,15 @@ import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.jvm.jvmErasure

internal sealed class ContextCommandAutoBuilder(
internal sealed class ContextCommandAutoBuilder<T : RootAnnotatedApplicationCommand>(
override val serviceContainer: ServiceContainer,
applicationConfig: BApplicationConfig,
private val resolverContainer: ResolverContainer
) : CommandAutoBuilder, GlobalApplicationCommandProvider, GuildApplicationCommandProvider {
) : ApplicationCommandAutoBuilder<T>(applicationConfig) {

protected abstract val commandAnnotation: KClass<out Annotation>
override val optionAnnotation: KClass<out Annotation> = ContextOption::class

protected val forceGuildCommands = applicationConfig.forceGuildCommands

protected fun ApplicationCommandBuilder<*>.processOptions(
guild: Guild?,
func: KFunction<*>,
Expand Down Expand Up @@ -80,4 +75,4 @@ internal sealed class ContextCommandAutoBuilder(
registry.serviceOption(parameter.declaredName)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,85 +7,62 @@ import io.github.freya022.botcommands.api.commands.application.annotations.Requi
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAMessageCommand
import io.github.freya022.botcommands.api.commands.application.context.message.GlobalMessageEvent
import io.github.freya022.botcommands.api.commands.application.provider.AbstractApplicationCommandManager
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandManager
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandManager
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
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.utils.findAnnotationRecursive
import io.github.freya022.botcommands.internal.commands.SkipLogger
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.MessageContextFunctionMetadata
import io.github.freya022.botcommands.internal.commands.autobuilder.*
import io.github.freya022.botcommands.internal.commands.autobuilder.castFunction
import io.github.freya022.botcommands.internal.core.requiredFilter
import io.github.freya022.botcommands.internal.core.service.FunctionAnnotationsMap
import io.github.freya022.botcommands.internal.parameters.ResolverContainer
import io.github.freya022.botcommands.internal.utils.FunctionFilter
import io.github.freya022.botcommands.internal.utils.annotationRef
import io.github.freya022.botcommands.internal.utils.throwInternal
import io.github.oshai.kotlinlogging.KotlinLogging
import net.dv8tion.jda.api.interactions.InteractionContextType
import net.dv8tion.jda.api.interactions.commands.Command.Type as CommandType
import kotlin.reflect.KClass

private val logger = KotlinLogging.logger { }

@BService
@RequiresApplicationCommands
internal class MessageContextCommandAutoBuilder(
applicationConfig: BApplicationConfig,
resolverContainer: ResolverContainer,
functionAnnotationsMap: FunctionAnnotationsMap,
serviceContainer: ServiceContainer
) : ContextCommandAutoBuilder(serviceContainer, applicationConfig, resolverContainer) {
override val commandAnnotation: KClass<out Annotation> get() = JDAMessageCommand::class

private val messageFunctions: List<MessageContextFunctionMetadata>
) : ContextCommandAutoBuilder<MessageContextFunctionMetadata>(serviceContainer, applicationConfig, resolverContainer) {

init {
messageFunctions = functionAnnotationsMap
.getWithClassAnnotation<Command, JDAMessageCommand>()
.requiredFilter(FunctionFilter.nonStatic())
.requiredFilter(FunctionFilter.firstArg(GlobalMessageEvent::class))
.map {
val func = it.function
val annotation = func.findAnnotationRecursive<JDAMessageCommand>() ?: throwInternal("${annotationRef<JDAMessageCommand>()} should be present")
val path = CommandPath.ofName(annotation.name)
val commandId = func.findAnnotationRecursive<CommandId>()?.value

MessageContextFunctionMetadata(it, annotation, path, commandId)
}
}
override val commandAnnotation: KClass<out Annotation> get() = JDAMessageCommand::class
override val commandType: CommandType
get() = CommandType.MESSAGE

//Separated functions so global command errors don't prevent guild commands from being registered
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) = declareMessage(manager)
override fun declareGuildApplicationCommands(manager: GuildApplicationCommandManager) = declareMessage(manager)
override val rootAnnotatedCommands = functionAnnotationsMap
.getWithClassAnnotation<Command, JDAMessageCommand>()
.requiredFilter(FunctionFilter.nonStatic())
.requiredFilter(FunctionFilter.firstArg(GlobalMessageEvent::class))
.map {
val func = it.function
val annotation = func.findAnnotationRecursive<JDAMessageCommand>() ?: throwInternal("${annotationRef<JDAMessageCommand>()} should be present")
val path = CommandPath.ofName(annotation.name)
val commandId = func.findAnnotationRecursive<CommandId>()?.value

private fun declareMessage(manager: AbstractApplicationCommandManager) {
with(SkipLogger(logger)) {
messageFunctions.forEachWithDelayedExceptions { metadata ->
runFiltered(
manager,
forceGuildCommands,
metadata,
metadata.annotation.scope
) { processMessageCommand(manager, metadata) }
}
log((manager as? GuildApplicationCommandManager)?.guild, CommandType.MESSAGE)
MessageContextFunctionMetadata(it, annotation, path, commandId)
}
}

context(_: SkipLogger)
private fun processMessageCommand(manager: AbstractApplicationCommandManager, metadata: MessageContextFunctionMetadata) {
val func = metadata.func
val instance = metadata.instance
val path = metadata.path
val commandId = metadata.commandId
context(logger: SkipLogger)
override fun declareTopLevel(
manager: AbstractApplicationCommandManager,
rootCommand: MessageContextFunctionMetadata,
) {
val func = rootCommand.func

if (!checkDeclarationFilter(manager, metadata.func, path, metadata.commandId))
if (!checkDeclarationFilter(manager, rootCommand))
return // Already logged

val annotation = metadata.annotation
manager.messageCommand(path.name, func.castFunction()) {
val annotation = rootCommand.annotation
manager.messageCommand(rootCommand.path.name, func.castFunction()) {
fillCommandBuilder(func)
fillApplicationCommandBuilder(func)

Expand All @@ -98,7 +75,7 @@ internal class MessageContextCommandAutoBuilder(
isDefaultLocked = annotation.defaultLocked
nsfw = annotation.nsfw

processOptions((manager as? GuildApplicationCommandManager)?.guild, func, instance, commandId)
processOptions((manager as? GuildApplicationCommandManager)?.guild, func, rootCommand.instance, rootCommand.commandId)
}
}
}
Loading