Skip to content

Commit be4801f

Browse files
committed
Move auto builder "utils" to respective auto builder abstractions
Also refactor the top-level filtering
1 parent c11a9e2 commit be4801f

File tree

13 files changed

+412
-406
lines changed

13 files changed

+412
-406
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.github.freya022.botcommands.internal.commands.application.autobuilder
2+
3+
import io.github.freya022.botcommands.api.commands.application.ApplicationCommandFilter
4+
import io.github.freya022.botcommands.api.commands.application.CommandScope
5+
import io.github.freya022.botcommands.api.commands.application.annotations.DeclarationFilter
6+
import io.github.freya022.botcommands.api.commands.application.annotations.Test
7+
import io.github.freya022.botcommands.api.commands.application.builder.ApplicationCommandBuilder
8+
import io.github.freya022.botcommands.api.commands.application.provider.*
9+
import io.github.freya022.botcommands.api.commands.text.annotations.NSFW
10+
import io.github.freya022.botcommands.api.core.BContext
11+
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
12+
import io.github.freya022.botcommands.api.core.objectLogger
13+
import io.github.freya022.botcommands.api.core.utils.findAllAnnotations
14+
import io.github.freya022.botcommands.api.core.utils.hasAnnotationRecursive
15+
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
16+
import io.github.freya022.botcommands.internal.commands.SkipLogger
17+
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.ApplicationFunctionMetadata
18+
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.RootAnnotatedApplicationCommand
19+
import io.github.freya022.botcommands.internal.commands.autobuilder.CommandAutoBuilder
20+
import io.github.freya022.botcommands.internal.commands.autobuilder.forEachWithDelayedExceptions
21+
import io.github.freya022.botcommands.internal.utils.*
22+
import net.dv8tion.jda.api.interactions.commands.Command as JDACommand
23+
import kotlin.reflect.KFunction
24+
25+
internal abstract class ApplicationCommandAutoBuilder<T : RootAnnotatedApplicationCommand>(
26+
applicationConfig: BApplicationConfig
27+
) : CommandAutoBuilder(),
28+
GlobalApplicationCommandProvider,
29+
GuildApplicationCommandProvider {
30+
31+
private val logger = this.objectLogger()
32+
33+
protected val forceGuildCommands: Boolean = applicationConfig.forceGuildCommands
34+
protected abstract val commandType: JDACommand.Type
35+
36+
protected abstract val rootAnnotatedCommands: Collection<T>
37+
38+
override fun declareGlobalApplicationCommands(manager: GlobalApplicationCommandManager) {
39+
// On global manager, do not register any command if forceGuildCommands is enabled,
40+
// as none of them would be global
41+
if (forceGuildCommands)
42+
return
43+
44+
val skipLogger = SkipLogger(logger)
45+
rootAnnotatedCommands.forEachWithDelayedExceptions forEach@{ rootAnnotatedCommand ->
46+
val scope = rootAnnotatedCommand.scope
47+
if (scope != CommandScope.GLOBAL) return@forEach
48+
49+
val testState = checkTestCommand(manager, rootAnnotatedCommand.func, scope, manager.context)
50+
if (testState != TestState.NO_ANNOTATION)
51+
throwInternal("Test commands on a global scope should have thrown in ${::checkTestCommand.shortSignatureNoSrc}")
52+
53+
context(skipLogger) { declareTopLevel(manager, rootAnnotatedCommand) }
54+
}
55+
56+
skipLogger.log(guild = null, commandType)
57+
}
58+
59+
override fun declareGuildApplicationCommands(manager: GuildApplicationCommandManager) {
60+
val skipLogger = SkipLogger(logger)
61+
rootAnnotatedCommands.forEachWithDelayedExceptions forEach@{ rootAnnotatedCommand ->
62+
val scope = rootAnnotatedCommand.scope
63+
64+
// If guild commands aren't forced, check the scope
65+
val canBeDeclared = forceGuildCommands || scope == CommandScope.GUILD
66+
if (!canBeDeclared) return@forEach
67+
68+
val testState = checkTestCommand(manager, rootAnnotatedCommand.func, scope, manager.context)
69+
if (scope == CommandScope.GLOBAL && testState != TestState.NO_ANNOTATION)
70+
throwInternal("Test commands on a global scope should have thrown in ${::checkTestCommand.shortSignatureNoSrc}")
71+
72+
if (testState == TestState.EXCLUDE)
73+
return@forEach skipLogger.skip(rootAnnotatedCommand.name, "Is a test command while this guild isn't a test guild")
74+
75+
context(skipLogger) { declareTopLevel(manager, rootAnnotatedCommand) }
76+
}
77+
78+
skipLogger.log(manager.guild, commandType)
79+
}
80+
81+
private fun checkTestCommand(manager: AbstractApplicationCommandManager, func: KFunction<*>, scope: CommandScope, context: BContext): TestState {
82+
if (func.hasAnnotationRecursive<Test>()) {
83+
requireAt(scope == CommandScope.GUILD, func) {
84+
"Test commands must have their scope set to GUILD"
85+
}
86+
if (manager !is GuildApplicationCommandManager) throwInternal("GUILD scoped command was not registered with a guild command manager")
87+
88+
//Returns whether the command can be registered
89+
return when (manager.guild.idLong) {
90+
in AnnotationUtils.getEffectiveTestGuildIds(context, func) -> TestState.INCLUDE
91+
else -> TestState.EXCLUDE
92+
}
93+
}
94+
95+
return TestState.NO_ANNOTATION
96+
}
97+
98+
context(logger: SkipLogger)
99+
protected abstract fun declareTopLevel(manager: AbstractApplicationCommandManager, rootCommand: T)
100+
101+
context(logger: SkipLogger)
102+
internal fun checkDeclarationFilter(
103+
manager: AbstractApplicationCommandManager,
104+
metadata: ApplicationFunctionMetadata<*>,
105+
): Boolean {
106+
val func = metadata.func
107+
val path = metadata.path
108+
val commandId = metadata.commandId
109+
110+
func.findAllAnnotations<DeclarationFilter>().forEach { declarationFilter ->
111+
checkAt(manager is GuildApplicationCommandManager, func) {
112+
"${annotationRef<DeclarationFilter>()} can only be used on guild commands"
113+
}
114+
115+
declarationFilter.filters.forEach {
116+
if (!serviceContainer.getService(it).filter(manager.guild, path, commandId)) {
117+
val commandIdStr = if (commandId != null) " (id ${commandId})" else ""
118+
logger.skip(path, "${it.simpleNestedName} rejected this command$commandIdStr")
119+
return false
120+
}
121+
}
122+
}
123+
return true
124+
}
125+
126+
protected fun ApplicationCommandBuilder<*>.fillApplicationCommandBuilder(func: KFunction<*>) {
127+
filters += AnnotationUtils.getFilters(context, func, ApplicationCommandFilter::class)
128+
129+
if (func.hasAnnotationRecursive<NSFW>()) {
130+
throwArgument(func, "${annotationRef<NSFW>()} can only be used on text commands, use the #nsfw method on your annotation instead")
131+
}
132+
}
133+
134+
private enum class TestState {
135+
INCLUDE,
136+
EXCLUDE,
137+
NO_ANNOTATION
138+
}
139+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/autobuilder/ContextCommandAutoBuilder.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@ import io.github.freya022.botcommands.api.commands.application.context.annotatio
88
import io.github.freya022.botcommands.api.commands.application.context.message.options.builder.MessageCommandOptionRegistry
99
import io.github.freya022.botcommands.api.commands.application.context.user.options.builder.UserCommandOptionRegistry
1010
import io.github.freya022.botcommands.api.commands.application.options.builder.ApplicationOptionRegistry
11-
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandProvider
12-
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandProvider
1311
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
1412
import io.github.freya022.botcommands.api.core.options.builder.inlineClassAggregate
1513
import io.github.freya022.botcommands.api.core.reflect.wrap
1614
import io.github.freya022.botcommands.api.core.service.ServiceContainer
1715
import io.github.freya022.botcommands.api.parameters.resolvers.ICustomResolver
16+
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.RootAnnotatedApplicationCommand
1817
import io.github.freya022.botcommands.internal.commands.application.autobuilder.utils.ParameterAdapter
19-
import io.github.freya022.botcommands.internal.commands.autobuilder.CommandAutoBuilder
20-
import io.github.freya022.botcommands.internal.commands.autobuilder.requireServiceOptionOrOptional
2118
import io.github.freya022.botcommands.internal.parameters.ResolverContainer
2219
import io.github.freya022.botcommands.internal.utils.ReflectionUtils.nonInstanceParameters
2320
import io.github.freya022.botcommands.internal.utils.findDeclarationName
@@ -26,17 +23,15 @@ import kotlin.reflect.KClass
2623
import kotlin.reflect.KFunction
2724
import kotlin.reflect.jvm.jvmErasure
2825

29-
internal sealed class ContextCommandAutoBuilder(
26+
internal sealed class ContextCommandAutoBuilder<T : RootAnnotatedApplicationCommand>(
3027
override val serviceContainer: ServiceContainer,
3128
applicationConfig: BApplicationConfig,
3229
private val resolverContainer: ResolverContainer
33-
) : CommandAutoBuilder, GlobalApplicationCommandProvider, GuildApplicationCommandProvider {
30+
) : ApplicationCommandAutoBuilder<T>(applicationConfig) {
3431

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

38-
protected val forceGuildCommands = applicationConfig.forceGuildCommands
39-
4035
protected fun ApplicationCommandBuilder<*>.processOptions(
4136
guild: Guild?,
4237
func: KFunction<*>,
@@ -80,4 +75,4 @@ internal sealed class ContextCommandAutoBuilder(
8075
registry.serviceOption(parameter.declaredName)
8176
}
8277
}
83-
}
78+
}

BotCommands-core/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/autobuilder/MessageContextCommandAutoBuilder.kt

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,85 +7,62 @@ import io.github.freya022.botcommands.api.commands.application.annotations.Requi
77
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAMessageCommand
88
import io.github.freya022.botcommands.api.commands.application.context.message.GlobalMessageEvent
99
import io.github.freya022.botcommands.api.commands.application.provider.AbstractApplicationCommandManager
10-
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandManager
1110
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandManager
1211
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
1312
import io.github.freya022.botcommands.api.core.service.ServiceContainer
1413
import io.github.freya022.botcommands.api.core.service.annotations.BService
1514
import io.github.freya022.botcommands.api.core.utils.findAnnotationRecursive
1615
import io.github.freya022.botcommands.internal.commands.SkipLogger
1716
import io.github.freya022.botcommands.internal.commands.application.autobuilder.metadata.MessageContextFunctionMetadata
18-
import io.github.freya022.botcommands.internal.commands.autobuilder.*
17+
import io.github.freya022.botcommands.internal.commands.autobuilder.castFunction
1918
import io.github.freya022.botcommands.internal.core.requiredFilter
2019
import io.github.freya022.botcommands.internal.core.service.FunctionAnnotationsMap
2120
import io.github.freya022.botcommands.internal.parameters.ResolverContainer
2221
import io.github.freya022.botcommands.internal.utils.FunctionFilter
2322
import io.github.freya022.botcommands.internal.utils.annotationRef
2423
import io.github.freya022.botcommands.internal.utils.throwInternal
25-
import io.github.oshai.kotlinlogging.KotlinLogging
2624
import net.dv8tion.jda.api.interactions.InteractionContextType
2725
import net.dv8tion.jda.api.interactions.commands.Command.Type as CommandType
2826
import kotlin.reflect.KClass
2927

30-
private val logger = KotlinLogging.logger { }
31-
3228
@BService
3329
@RequiresApplicationCommands
3430
internal class MessageContextCommandAutoBuilder(
3531
applicationConfig: BApplicationConfig,
3632
resolverContainer: ResolverContainer,
3733
functionAnnotationsMap: FunctionAnnotationsMap,
3834
serviceContainer: ServiceContainer
39-
) : ContextCommandAutoBuilder(serviceContainer, applicationConfig, resolverContainer) {
40-
override val commandAnnotation: KClass<out Annotation> get() = JDAMessageCommand::class
41-
42-
private val messageFunctions: List<MessageContextFunctionMetadata>
35+
) : ContextCommandAutoBuilder<MessageContextFunctionMetadata>(serviceContainer, applicationConfig, resolverContainer) {
4336

44-
init {
45-
messageFunctions = functionAnnotationsMap
46-
.getWithClassAnnotation<Command, JDAMessageCommand>()
47-
.requiredFilter(FunctionFilter.nonStatic())
48-
.requiredFilter(FunctionFilter.firstArg(GlobalMessageEvent::class))
49-
.map {
50-
val func = it.function
51-
val annotation = func.findAnnotationRecursive<JDAMessageCommand>() ?: throwInternal("${annotationRef<JDAMessageCommand>()} should be present")
52-
val path = CommandPath.ofName(annotation.name)
53-
val commandId = func.findAnnotationRecursive<CommandId>()?.value
54-
55-
MessageContextFunctionMetadata(it, annotation, path, commandId)
56-
}
57-
}
37+
override val commandAnnotation: KClass<out Annotation> get() = JDAMessageCommand::class
38+
override val commandType: CommandType
39+
get() = CommandType.MESSAGE
5840

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

63-
private fun declareMessage(manager: AbstractApplicationCommandManager) {
64-
with(SkipLogger(logger)) {
65-
messageFunctions.forEachWithDelayedExceptions { metadata ->
66-
runFiltered(
67-
manager,
68-
forceGuildCommands,
69-
metadata,
70-
metadata.annotation.scope
71-
) { processMessageCommand(manager, metadata) }
72-
}
73-
log((manager as? GuildApplicationCommandManager)?.guild, CommandType.MESSAGE)
51+
MessageContextFunctionMetadata(it, annotation, path, commandId)
7452
}
75-
}
7653

77-
context(_: SkipLogger)
78-
private fun processMessageCommand(manager: AbstractApplicationCommandManager, metadata: MessageContextFunctionMetadata) {
79-
val func = metadata.func
80-
val instance = metadata.instance
81-
val path = metadata.path
82-
val commandId = metadata.commandId
54+
context(logger: SkipLogger)
55+
override fun declareTopLevel(
56+
manager: AbstractApplicationCommandManager,
57+
rootCommand: MessageContextFunctionMetadata,
58+
) {
59+
val func = rootCommand.func
8360

84-
if (!checkDeclarationFilter(manager, metadata.func, path, metadata.commandId))
61+
if (!checkDeclarationFilter(manager, rootCommand))
8562
return // Already logged
8663

87-
val annotation = metadata.annotation
88-
manager.messageCommand(path.name, func.castFunction()) {
64+
val annotation = rootCommand.annotation
65+
manager.messageCommand(rootCommand.path.name, func.castFunction()) {
8966
fillCommandBuilder(func)
9067
fillApplicationCommandBuilder(func)
9168

@@ -98,7 +75,7 @@ internal class MessageContextCommandAutoBuilder(
9875
isDefaultLocked = annotation.defaultLocked
9976
nsfw = annotation.nsfw
10077

101-
processOptions((manager as? GuildApplicationCommandManager)?.guild, func, instance, commandId)
78+
processOptions((manager as? GuildApplicationCommandManager)?.guild, func, rootCommand.instance, rootCommand.commandId)
10279
}
10380
}
10481
}

0 commit comments

Comments
 (0)