Skip to content

Commit 75fa008

Browse files
authored
Add support for user-installable apps (#163)
1 parent 8471d34 commit 75fa008

38 files changed

+916
-151
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@
478478
<dependency>
479479
<groupId>net.dv8tion</groupId>
480480
<artifactId>JDA</artifactId>
481-
<version>5.2.2</version>
481+
<version>5.3.0</version>
482482
<exclusions>
483483
<exclusion>
484484
<groupId>club.minnced</groupId>
Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,34 @@
11
package io.github.freya022.botcommands.api.commands.application;
22

33
import io.github.freya022.botcommands.api.commands.application.annotations.DeclarationFilter;
4-
import io.github.freya022.botcommands.api.commands.application.annotations.Test;
4+
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAMessageCommand;
5+
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAUserCommand;
6+
import io.github.freya022.botcommands.api.commands.application.slash.annotations.TopLevelSlashCommandData;
57
import io.github.freya022.botcommands.api.core.config.BApplicationConfigBuilder;
8+
import net.dv8tion.jda.api.interactions.InteractionContextType;
69

710
/**
8-
* Defines command scopes for application commands.
11+
* Defines the scope on which an application command is pushed to.
912
*/
1013
public enum CommandScope {
1114
/**
12-
* The guild command scope, only pushes application commands to the guilds
13-
* <br>Can be filtered with {@link DeclarationFilter @DeclarationFilter}.
14-
* <br>Can be forced with {@link BApplicationConfigBuilder#forceGuildCommands(boolean)} and {@link Test @Test}
15+
* The guild command scope, making the application command accessible on a per-guild basis.
16+
* <br>These commands can only be executed in the guild they are pushed to.
17+
*
18+
* <p>Can be filtered with {@link DeclarationFilter @DeclarationFilter}.
19+
* <br>Can be forced with {@link BApplicationConfigBuilder#forceGuildCommands(boolean)}.
1520
*/
16-
GUILD(false, true),
21+
GUILD,
1722
/**
18-
* The global command scope, pushes this command to the first shard
23+
* The global command scope, making the application command be accessible
24+
* in the {@link InteractionContextType interaction contexts} set on the command.
1925
*
20-
* <p>Cannot be filtered on a per-guild basis
26+
* <p>These commands cannot be filtered.
2127
*/
22-
GLOBAL(true, false),
28+
GLOBAL,
2329
/**
24-
* The global command scope, but with DMs disabled, pushes this command to the first shard
25-
* <br>This might be useful to have guild commands but without having to push them on every guild
26-
*
27-
* <p>Cannot be filtered on a per-guild basis
30+
* @deprecated Replace this by setting {@link TopLevelSlashCommandData#contexts()}/{@link JDAMessageCommand#contexts()}/{@link JDAUserCommand#contexts()} to {@link InteractionContextType#GUILD}
2831
*/
29-
GLOBAL_NO_DM(true, true);
30-
31-
private final boolean isGlobal;
32-
private final boolean guildOnly;
33-
34-
CommandScope(boolean isGlobal, boolean guildOnly) {
35-
this.isGlobal = isGlobal;
36-
this.guildOnly = guildOnly;
37-
}
38-
39-
public boolean isGlobal() {
40-
return isGlobal;
41-
}
42-
43-
public boolean isGuildOnly() {
44-
return guildOnly;
45-
}
32+
@Deprecated(forRemoval = true)
33+
GLOBAL_NO_DM;
4634
}

src/main/kotlin/io/github/freya022/botcommands/api/commands/application/ApplicationCommandResolverData.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package io.github.freya022.botcommands.api.commands.application
22

33
import io.github.freya022.botcommands.api.commands.application.builder.ApplicationCommandBuilder
4-
import io.github.freya022.botcommands.api.core.reflect.throwUser
4+
import io.github.freya022.botcommands.api.core.reflect.requireUser
55
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
66
import io.github.freya022.botcommands.api.parameters.ResolverData
77
import io.github.freya022.botcommands.api.parameters.ResolverRequest
88
import io.github.freya022.botcommands.api.parameters.resolvers.MessageContextParameterResolver
99
import io.github.freya022.botcommands.api.parameters.resolvers.SlashParameterResolver
1010
import io.github.freya022.botcommands.api.parameters.resolvers.UserContextParameterResolver
11+
import net.dv8tion.jda.api.interactions.InteractionContextType
1112
import kotlin.reflect.KClass
1213

1314
/**
@@ -22,9 +23,14 @@ class ApplicationCommandResolverData internal constructor(
2223

2324
internal fun ResolverRequest.checkGuildOnly(returnType: KClass<*>) {
2425
(resolverData as? ApplicationCommandResolverData)?.let { data ->
25-
//TODO[User apps] throw if there is no guild scope at all, regardless of nullability
26-
if (!data.commandBuilder.topLevelBuilder.scope.isGuildOnly && parameter.isRequired) {
27-
parameter.throwUser("Cannot get a required ${returnType.simpleNestedName} in a global command")
26+
parameter.requireUser(InteractionContextType.GUILD in data.commandBuilder.topLevelBuilder.contexts) {
27+
"Commands that cannot run in guilds can't have a ${returnType.simpleNestedName} option"
28+
}
29+
30+
if (parameter.isRequired) {
31+
parameter.requireUser(data.commandBuilder.topLevelBuilder.isGuildOnly) {
32+
"Commands executable outside of guilds cannot have a required ${returnType.simpleNestedName} option"
33+
}
2834
}
2935
}
3036
}

src/main/kotlin/io/github/freya022/botcommands/api/commands/application/TopLevelApplicationCommandInfo.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.github.freya022.botcommands.api.commands.application
33
import io.github.freya022.botcommands.internal.utils.throwState
44
import net.dv8tion.jda.api.entities.ISnowflake
55
import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel
6+
import net.dv8tion.jda.api.interactions.IntegrationType
7+
import net.dv8tion.jda.api.interactions.InteractionContextType
68
import net.dv8tion.jda.api.interactions.commands.privileges.IntegrationPrivilege
79
import net.dv8tion.jda.api.requests.RestAction
810
import java.time.OffsetDateTime
@@ -14,9 +16,14 @@ import java.time.OffsetDateTime
1416
*/
1517
interface TopLevelApplicationCommandInfo : ApplicationCommandInfo, TopLevelApplicationCommandMetadata, ISnowflake {
1618
/**
17-
* The scope on which this application command is pushed on.
19+
* Represents where a command can be used.
1820
*/
19-
val scope: CommandScope
21+
val contexts: Set<InteractionContextType>
22+
23+
/**
24+
* The integration types in which this command can be installed in.
25+
*/
26+
val integrationTypes: Set<IntegrationType>
2027

2128
/**
2229
* Whether this application command is (initially) locked to administrators.
@@ -29,6 +36,7 @@ interface TopLevelApplicationCommandInfo : ApplicationCommandInfo, TopLevelAppli
2936
* Whether this application command is usable only in guilds (i.e., no DMs).
3037
*/
3138
val isGuildOnly: Boolean
39+
get() = contexts.singleOrNull() == InteractionContextType.GUILD
3240

3341
/**
3442
* Whether this application commands is usable only in [NSFW channels][IAgeRestrictedChannel].

src/main/kotlin/io/github/freya022/botcommands/api/commands/application/builder/TopLevelApplicationCommandBuilder.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,52 @@ import io.github.freya022.botcommands.api.commands.application.CommandScope
44
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAMessageCommand
55
import io.github.freya022.botcommands.api.commands.application.context.annotations.JDAUserCommand
66
import io.github.freya022.botcommands.api.commands.application.options.builder.ApplicationCommandOptionAggregateBuilder
7+
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandManager
8+
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandManager
79
import io.github.freya022.botcommands.api.commands.application.slash.annotations.TopLevelSlashCommandData
10+
import io.github.freya022.botcommands.api.core.utils.enumSetOf
811
import net.dv8tion.jda.api.Permission
12+
import net.dv8tion.jda.api.interactions.IntegrationType
13+
import net.dv8tion.jda.api.interactions.InteractionContextType
14+
import java.util.*
915

1016
interface TopLevelApplicationCommandBuilder<T : ApplicationCommandOptionAggregateBuilder<T>> : ApplicationCommandBuilder<T> {
1117
/**
1218
* @see TopLevelSlashCommandData.scope
1319
* @see JDAUserCommand.scope
1420
* @see JDAMessageCommand.scope
1521
*/
22+
@Deprecated("Replaced with interaction contexts")
1623
val scope: CommandScope
24+
get() = when (EnumSet.copyOf(contexts)) {
25+
enumSetOf(InteractionContextType.GUILD) -> CommandScope.GUILD
26+
enumSetOf(InteractionContextType.GUILD, InteractionContextType.BOT_DM) -> CommandScope.GLOBAL
27+
else -> throw IllegalArgumentException("Cannot map $contexts to a CommandScope")
28+
}
29+
30+
/**
31+
* Represents where a command can be used.
32+
*
33+
* **Default:** [GlobalApplicationCommandManager.Defaults.contexts] or [GuildApplicationCommandManager.Defaults.contexts]
34+
*
35+
* @see InteractionContextType
36+
* @see TopLevelSlashCommandData.contexts
37+
* @see JDAUserCommand.contexts
38+
* @see JDAMessageCommand.contexts
39+
*/
40+
var contexts: Set<InteractionContextType>
41+
42+
/**
43+
* The integration types in which this command can be installed in.
44+
*
45+
* **Default:** [GlobalApplicationCommandManager.Defaults.integrationTypes] or [GuildApplicationCommandManager.Defaults.integrationTypes]
46+
*
47+
* @see IntegrationType
48+
* @see TopLevelSlashCommandData.integrationTypes
49+
* @see JDAUserCommand.integrationTypes
50+
* @see JDAMessageCommand.integrationTypes
51+
*/
52+
var integrationTypes: Set<IntegrationType>
1753

1854
/**
1955
* Specifies whether the application command is disabled for everyone but administrators by default,
@@ -51,4 +87,11 @@ interface TopLevelApplicationCommandBuilder<T : ApplicationCommandOptionAggregat
5187
* @see JDAMessageCommand.nsfw
5288
*/
5389
var nsfw: Boolean
90+
91+
/**
92+
* Whether this command can only be used in guilds.
93+
*
94+
* This is equivalent to `contexts == setOf(InteractionContextType.GUILD)`.
95+
*/
96+
val isGuildOnly: Boolean get() = contexts.singleOrNull() == InteractionContextType.GUILD
5497
}

src/main/kotlin/io/github/freya022/botcommands/api/commands/application/context/annotations/JDAMessageCommand.kt

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import io.github.freya022.botcommands.api.commands.application.CommandScope
66
import io.github.freya022.botcommands.api.commands.application.context.message.GlobalMessageEvent
77
import io.github.freya022.botcommands.api.commands.application.context.message.GuildMessageEvent
88
import io.github.freya022.botcommands.api.commands.application.context.message.builder.MessageCommandBuilder
9-
import io.github.freya022.botcommands.api.commands.application.provider.AbstractApplicationCommandManager
10-
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandProvider
11-
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandProvider
9+
import io.github.freya022.botcommands.api.commands.application.provider.*
10+
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
1211
import io.github.freya022.botcommands.api.localization.annotations.LocalizationBundle
1312
import io.github.freya022.botcommands.api.localization.context.AppLocalizationContext
1413
import io.github.freya022.botcommands.api.parameters.ParameterResolver
1514
import io.github.freya022.botcommands.api.parameters.resolvers.ICustomResolver
1615
import io.github.freya022.botcommands.api.parameters.resolvers.MessageContextParameterResolver
16+
import net.dv8tion.jda.api.interactions.IntegrationType
17+
import net.dv8tion.jda.api.interactions.InteractionContextType
1718
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction
1819

1920
/**
@@ -24,8 +25,11 @@ import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFuncti
2425
*
2526
* ### Requirements
2627
* - The declaring class must be annotated with [@Command][Command] and extend [ApplicationCommand].
27-
* - First parameter must be [GlobalMessageEvent] for [global][CommandScope.GLOBAL] commands, or,
28-
* [GuildMessageEvent] for [global guild-only][CommandScope.GLOBAL_NO_DM] and [guild][CommandScope.GUILD] commands.
28+
*
29+
* The first parameter must be:
30+
* - [GuildMessageEvent] if the [interaction context][contexts]
31+
* only contains [InteractionContextType.GUILD].
32+
* - [GlobalMessageEvent] in other cases.
2933
*
3034
* ### Option types
3135
* - Input options: Uses [@ContextOption][ContextOption], supported types and modifiers are in [ParameterResolver],
@@ -53,11 +57,39 @@ import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFuncti
5357
@Retention(AnnotationRetention.RUNTIME)
5458
annotation class JDAMessageCommand(
5559
/**
56-
* Specifies the application command scope for this command.
60+
* Specifies the application command scope for this command, where the command will be pushed to.
61+
*
62+
* This will be forced to [CommandScope.GUILD] if [BApplicationConfig.forceGuildCommands] is enabled.
63+
*
64+
* **Default:** [CommandScope.GLOBAL]
65+
*/
66+
val scope: CommandScope = CommandScope.GLOBAL,
67+
68+
/**
69+
* Represents where a command can be used.
70+
*
71+
* **Default, depending on [scope]:**
72+
* - [Global][CommandScope.GLOBAL] : [GlobalApplicationCommandManager.Defaults.contexts]
73+
* - [Guild][CommandScope.GUILD] : [GuildApplicationCommandManager.Defaults.contexts]
74+
*
75+
* This will be forced to [InteractionContextType.GUILD] if [BApplicationConfig.forceGuildCommands] is enabled.
76+
*
77+
* @see InteractionContextType
78+
* @see MessageCommandBuilder.contexts
79+
*/
80+
val contexts: Array<InteractionContextType> = [],
81+
82+
/**
83+
* The integration types in which this command can be installed in.
84+
*
85+
* **Default, depending on [scope]:**
86+
* - [Global][CommandScope.GLOBAL] : [GlobalApplicationCommandManager.Defaults.integrationTypes]
87+
* - [Guild][CommandScope.GUILD] : [GuildApplicationCommandManager.Defaults.integrationTypes]
5788
*
58-
* **Default:** [CommandScope.GLOBAL_NO_DM]
89+
* @see IntegrationType
90+
* @see MessageCommandBuilder.integrationTypes
5991
*/
60-
val scope: CommandScope = CommandScope.GLOBAL_NO_DM,
92+
val integrationTypes: Array<IntegrationType> = [],
6193

6294
/**
6395
* Specifies whether the application command is disabled for everyone but administrators by default,

src/main/kotlin/io/github/freya022/botcommands/api/commands/application/context/annotations/JDAUserCommand.kt

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ import io.github.freya022.botcommands.api.commands.application.CommandScope
66
import io.github.freya022.botcommands.api.commands.application.context.user.GlobalUserEvent
77
import io.github.freya022.botcommands.api.commands.application.context.user.GuildUserEvent
88
import io.github.freya022.botcommands.api.commands.application.context.user.builder.UserCommandBuilder
9-
import io.github.freya022.botcommands.api.commands.application.provider.AbstractApplicationCommandManager
10-
import io.github.freya022.botcommands.api.commands.application.provider.GlobalApplicationCommandProvider
11-
import io.github.freya022.botcommands.api.commands.application.provider.GuildApplicationCommandProvider
9+
import io.github.freya022.botcommands.api.commands.application.provider.*
10+
import io.github.freya022.botcommands.api.core.config.BApplicationConfig
1211
import io.github.freya022.botcommands.api.core.entities.InputUser
1312
import io.github.freya022.botcommands.api.localization.annotations.LocalizationBundle
1413
import io.github.freya022.botcommands.api.localization.context.AppLocalizationContext
1514
import io.github.freya022.botcommands.api.parameters.ParameterResolver
1615
import io.github.freya022.botcommands.api.parameters.resolvers.ICustomResolver
1716
import io.github.freya022.botcommands.api.parameters.resolvers.UserContextParameterResolver
17+
import net.dv8tion.jda.api.interactions.IntegrationType
18+
import net.dv8tion.jda.api.interactions.InteractionContextType
1819
import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFunction
1920

2021
/**
@@ -25,8 +26,11 @@ import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFuncti
2526
*
2627
* ### Requirements
2728
* - The declaring class must be annotated with [@Command][Command] and extend [ApplicationCommand].
28-
* - First parameter must be [GlobalUserEvent] for [global][CommandScope.GLOBAL] commands, or,
29-
* [GuildUserEvent] for [global guild-only][CommandScope.GLOBAL_NO_DM] and [guild][CommandScope.GUILD] commands.
29+
*
30+
* The first parameter must be:
31+
* - [GuildUserEvent] if the [interaction context][contexts]
32+
* only contains [InteractionContextType.GUILD].
33+
* - [GlobalUserEvent] in other cases.
3034
*
3135
* ### Option types
3236
* - Input options: Uses [@ContextOption][ContextOption], supported types and modifiers are in [ParameterResolver],
@@ -55,11 +59,39 @@ import net.dv8tion.jda.api.interactions.commands.localization.LocalizationFuncti
5559
@Retention(AnnotationRetention.RUNTIME)
5660
annotation class JDAUserCommand(
5761
/**
58-
* Specifies the application command scope for this command.
62+
* Specifies the application command scope for this command, where the command will be pushed to.
63+
*
64+
* This will be forced to [CommandScope.GUILD] if [BApplicationConfig.forceGuildCommands] is enabled.
65+
*
66+
* **Default:** [CommandScope.GLOBAL]
67+
*/
68+
val scope: CommandScope = CommandScope.GLOBAL,
69+
70+
/**
71+
* Represents where a command can be used.
72+
*
73+
* **Default, depending on [scope]:**
74+
* - [Global][CommandScope.GLOBAL] : [GlobalApplicationCommandManager.Defaults.contexts]
75+
* - [Guild][CommandScope.GUILD] : [GuildApplicationCommandManager.Defaults.contexts]
76+
*
77+
* This will be forced to [InteractionContextType.GUILD] if [BApplicationConfig.forceGuildCommands] is enabled.
78+
*
79+
* @see InteractionContextType
80+
* @see UserCommandBuilder.contexts
81+
*/
82+
val contexts: Array<InteractionContextType> = [],
83+
84+
/**
85+
* The integration types in which this command can be installed in.
86+
*
87+
* **Default, depending on [scope]:**
88+
* - [Global][CommandScope.GLOBAL] : [GlobalApplicationCommandManager.Defaults.integrationTypes]
89+
* - [Guild][CommandScope.GUILD] : [GuildApplicationCommandManager.Defaults.integrationTypes]
5990
*
60-
* **Default:** [CommandScope.GLOBAL_NO_DM]
91+
* @see IntegrationType
92+
* @see UserCommandBuilder.integrationTypes
6193
*/
62-
val scope: CommandScope = CommandScope.GLOBAL_NO_DM,
94+
val integrationTypes: Array<IntegrationType> = [],
6395

6496
/**
6597
* Specifies whether the application command is disabled for everyone but administrators by default,

0 commit comments

Comments
 (0)