diff --git a/.idea/misc.xml b/.idea/misc.xml index a7dd9f9b93..0de8ea0f30 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt index d8169da064..577ed38e03 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt @@ -132,6 +132,8 @@ public abstract class ApplicationCommandRegistry : KordExKoinComponent { */ public abstract suspend fun register(command: UserCommand<*, *>): UserCommand<*, *>? + public abstract suspend fun register(command: PrimaryEntryPointCommand): Snowflake + /** Event handler for slash commands. **/ public abstract suspend fun handle(event: ChatInputCommandInteractionCreateEvent) @@ -236,6 +238,7 @@ public abstract class ApplicationCommandRegistry : KordExKoinComponent { is SlashCommand<*, *, *> -> createDiscordSlashCommand(command) is UserCommand<*, *> -> createDiscordUserCommand(command) is MessageCommand<*, *> -> createDiscordMessageCommand(command) + is PrimaryEntryPointCommand -> createDiscordEntryPointCommand(command) else -> throw IllegalArgumentException("Unknown ApplicationCommand type") } @@ -356,9 +359,50 @@ public abstract class ApplicationCommandRegistry : KordExKoinComponent { return response.id } + public open suspend fun createDiscordEntryPointCommand(command: PrimaryEntryPointCommand): Snowflake { + val locale = bot.settings.i18nBuilder.defaultLocale + + val (name, nameLocalizations) = command.localizedName + val (description, descriptionLocalizations) = command.localizedDescription + + val guild = if (command.guildId != null) { + kord.getGuildOrNull(command.guildId!!) + } else { + null + } + + val response = if (guild == null) { + kord.createGlobalEntryPointCommand(name, description, command.handler) { + this.nameLocalizations = nameLocalizations + this.descriptionLocalizations = descriptionLocalizations + + this.register(locale, command) + } + } else { + kord.createGuildEntryPointCommand(guild.id, name, description, command.handler) { + this.nameLocalizations = nameLocalizations + this.descriptionLocalizations = descriptionLocalizations + + this.register(locale, command) + } + } + + return response.id + } + // endregion // region: Extensions + + /** Registration logic for entry points, extracted for clarity. **/ + public open suspend fun EntryPointCreateBuilder.register(locale: Locale, command: PrimaryEntryPointCommand) { + if (this is GlobalEntryPointCreateBuilder) { + registerGlobalPermissions(locale, command) + } else { + registerGuildPermissions(locale, command) + } + } + /** Registration logic for slash commands, extracted for clarity. **/ public open suspend fun ChatInputCreateBuilder.register(locale: Locale, command: SlashCommand<*, *, *>) { if (this is GlobalChatInputCreateBuilder) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt index 884ee83d8b..9e75900104 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt @@ -25,6 +25,7 @@ import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.rest.builder.interaction.MultiApplicationCommandBuilder +import dev.kord.rest.builder.interaction.entryPoint import dev.kord.rest.builder.interaction.input import dev.kord.rest.builder.interaction.message import dev.kord.rest.builder.interaction.user @@ -209,6 +210,17 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry this.register(locale, it) } } + + is PrimaryEntryPointCommand -> { + val (description, descriptionLocalizations) = it.localizedDescription + + entryPoint(name, description, it.handler) { + this.nameLocalizations = nameLocalizations + this.descriptionLocalizations = descriptionLocalizations + + this.register(locale, it) + } + } } } } @@ -290,6 +302,8 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry return command } + override suspend fun register(command: PrimaryEntryPointCommand): Snowflake = createDiscordEntryPointCommand(command) + // endregion // region: Unregistration functions diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PrimaryEntryPointCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PrimaryEntryPointCommand.kt new file mode 100644 index 0000000000..17287c740b --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PrimaryEntryPointCommand.kt @@ -0,0 +1,32 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.kotlindiscord.kord.extensions.commands.application + +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.utils.MutableStringKeyedMap +import dev.kord.common.entity.ApplicationCommandType +import dev.kord.common.entity.EntryPointCommandHandlerType + +public open class PrimaryEntryPointCommand(extension: Extension) : ApplicationCommand(extension) { + + public lateinit var handler: EntryPointCommandHandlerType + + /** Command description, as displayed on Discord. **/ + public open lateinit var description: String + + /** + * A [Localized] version of [description]. + */ + public val localizedDescription: Localized by lazy { localize(description) } + + override val type: ApplicationCommandType = ApplicationCommandType.PrimaryEntryPoint + + override suspend fun call( + event: Nothing, + cache: MutableStringKeyedMap, + ): Nothing = error("Primary entry point commands can't be called") +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt index 9c2f3517a6..ea01a94bea 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt @@ -65,6 +65,8 @@ public open class StorageAwareApplicationCommandRegistry( return command } + override suspend fun register(command: PrimaryEntryPointCommand): Snowflake = createDiscordEntryPointCommand(command) + override suspend fun register(command: UserCommand<*, *>): UserCommand<*, *>? { val commandId = createDiscordCommand(command) ?: return null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt index 271d9ecde0..7680d3a787 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/Extension.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.checks.types.UserCommandCheck import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry +import com.kotlindiscord.kord.extensions.commands.application.PrimaryEntryPointCommand import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand @@ -107,6 +108,8 @@ public abstract class Extension : KordExKoinComponent { */ public open val userCommands: MutableList> = mutableListOf() + public open val entryPointCommands: MutableList = mutableListOf() + /** * List of chat command checks. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt index d4fbfd2552..fef43d3b58 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.checks.types.MessageCommandCheck import com.kotlindiscord.kord.extensions.checks.types.SlashCommandCheck import com.kotlindiscord.kord.extensions.checks.types.UserCommandCheck import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.PrimaryEntryPointCommand import com.kotlindiscord.kord.extensions.commands.application.message.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.application.message.PublicMessageCommand import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommand @@ -478,6 +479,17 @@ public suspend fun Extension.ephemeralUserCommand( return ephemeralUserCommand(commandObj) } +/** Register an ephemeral entry point, DSL-style. **/ +@ExtensionDSL +public suspend fun Extension.primaryEntryPointCommand( + body: suspend PrimaryEntryPointCommand.() -> Unit, +): PrimaryEntryPointCommand { + val commandObj = PrimaryEntryPointCommand(this) + body(commandObj) + + return primaryEntryPointCommand(commandObj) +} + /** Register an ephemeral user command, DSL-style. **/ @ExtensionDSL public suspend fun Extension.ephemeralUserCommand( @@ -511,6 +523,27 @@ public suspend fun Extension.ephemeralUserCommand( return commandObj } +/** Register a custom instance of an ephemeral user command. **/ +@ExtensionDSL +public suspend fun Extension.primaryEntryPointCommand( + commandObj: PrimaryEntryPointCommand, +): PrimaryEntryPointCommand { + try { + commandObj.validate() + entryPointCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } + } + + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + + return commandObj +} + /** Register a public user command, DSL-style. **/ @ExtensionDSL public suspend fun Extension.publicUserCommand(