From 68554017f4a402dd5d00c90063e265f1fbdc23de Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 21 Aug 2021 10:47:13 +0100 Subject: [PATCH 001/131] Back to snapshots --- gradle.properties | 2 +- libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9e583586fa..41f95ef885 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ kotlin.incremental = true ksp.incremental = false -projectVersion = 1.4.4-RC4 +projectVersion = 1.5.0-SNAPSHOT diff --git a/libs.versions.toml b/libs.versions.toml index 117bc52867..e5268d786d 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -10,8 +10,8 @@ icu4j = "69.1" junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" -kord = "0.8.0-M4" -#kord = "0.8.x-SNAPSHOT" +#kord = "0.8.0-M4" +kord = "0.8.x-SNAPSHOT" kotlinpoet = "1.8.0" ksp = "1.5.10-1.0.0-beta02" kx-ser = "1.2.1" From adcad98bf433789b297064cf52f170570d8ad9a1 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 21 Aug 2021 11:10:54 +0100 Subject: [PATCH 002/131] First snapshot targeting Kord's contexts branch --- .../kord/extensions/ExtensibleBot.kt | 4 +- .../builders/ExtensibleBotBuilder.kt | 8 ++-- .../kord/extensions/checks/CheckUtils.kt | 16 ++++---- .../extensions/commands/slash/SlashCommand.kt | 19 +++++---- .../commands/slash/SlashCommandContext.kt | 6 +-- .../commands/slash/SlashCommandRegistry.kt | 12 +++--- .../kord/extensions/components/Components.kt | 6 +-- .../builders/ActionableComponentBuilder.kt | 18 ++++---- .../components/builders/ComponentBuilder.kt | 4 +- .../builders/InteractiveButtonBuilder.kt | 4 +- .../components/builders/MenuBuilder.kt | 4 +- .../contexts/ActionableComponentContext.kt | 4 +- .../contexts/InteractiveButtonContext.kt | 5 +-- .../components/contexts/MenuContext.kt | 4 +- .../kord/extensions/extensions/Extension.kt | 8 ++-- .../pagination/BaseButtonPaginator.kt | 4 +- .../kord/extensions/utils/_Translation.kt | 41 +++++++++++++++++-- libs.versions.toml | 2 +- 18 files changed, 103 insertions(+), 66 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 10c3014c64..6bf741f09a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -20,7 +20,7 @@ import dev.kord.core.event.Event import dev.kord.core.event.gateway.DisconnectEvent import dev.kord.core.event.gateway.ReadyEvent import dev.kord.core.event.guild.GuildCreateEvent -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.on import dev.kord.gateway.Intents @@ -202,7 +202,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } if (settings.slashCommandsBuilder.enabled) { - on { + on { getKoin().get().handle(this) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 661862b1df..5a39c9e6b9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -27,7 +27,7 @@ import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.builder.kord.Shards import dev.kord.core.cache.KordCacheBuilder -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy @@ -875,7 +875,7 @@ public open class ExtensibleBotBuilder { * * These checks will be checked against all slash commands. */ - public val checkList: MutableList> = mutableListOf() + public val checkList: MutableList> = mutableListOf() /** Set a guild ID to use for all global slash commands. Intended for testing. **/ public fun defaultGuild(id: Snowflake) { @@ -912,7 +912,7 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun check(vararg checks: Check) { + public fun check(vararg checks: Check) { checks.forEach { checkList.add(it) } } @@ -921,7 +921,7 @@ public open class ExtensibleBotBuilder { * * @param check Check to apply to all slash commands. */ - public fun check(check: Check) { + public fun check(check: Check) { checkList.add(check) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 6cc7e9450a..6f422a6d28 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -9,12 +9,12 @@ import dev.kord.core.behavior.* import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.core.entity.channel.thread.ThreadChannel -import dev.kord.core.entity.interaction.GuildInteraction +import dev.kord.core.entity.interaction.GuildApplicationCommandInteraction import dev.kord.core.event.Event import dev.kord.core.event.channel.* import dev.kord.core.event.channel.thread.* import dev.kord.core.event.guild.* -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ApplicationCreateEvent import dev.kord.core.event.message.* import dev.kord.core.event.role.RoleCreateEvent import dev.kord.core.event.role.RoleDeleteEvent @@ -40,7 +40,7 @@ public suspend fun channelFor(event: Event): ChannelBehavior? { is ChannelDeleteEvent -> event.channel is ChannelPinsUpdateEvent -> event.channel is ChannelUpdateEvent -> event.channel - is InteractionCreateEvent -> event.interaction.channel + is ApplicationCreateEvent -> event.interaction.channel is InviteCreateEvent -> event.channel is InviteDeleteEvent -> event.channel is MessageBulkDeleteEvent -> event.channel @@ -104,7 +104,7 @@ public suspend fun channelIdFor(event: Event): Long? { is ChannelDeleteEvent -> event.channel.id.value is ChannelPinsUpdateEvent -> event.channel.id.value is ChannelUpdateEvent -> event.channel.id.value - is InteractionCreateEvent -> event.interaction.channel.id.value + is ApplicationCreateEvent -> event.interaction.channel.id.value is InviteCreateEvent -> event.channel.id.value is InviteDeleteEvent -> event.channel.id.value is MessageBulkDeleteEvent -> event.channelId.value @@ -146,7 +146,7 @@ public suspend fun channelSnowflakeFor(event: Event): Snowflake? { is ChannelDeleteEvent -> event.channel.id is ChannelPinsUpdateEvent -> event.channel.id is ChannelUpdateEvent -> event.channel.id - is InteractionCreateEvent -> event.interaction.channel.id + is ApplicationCreateEvent -> event.interaction.channel.id is InviteCreateEvent -> event.channel.id is InviteDeleteEvent -> event.channel.id is MessageBulkDeleteEvent -> event.channelId @@ -196,7 +196,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? { is GuildUpdateEvent -> event.guild is IntegrationsUpdateEvent -> event.guild - is InteractionCreateEvent -> { + is ApplicationCreateEvent -> { val guildId = event.interaction.data.guildId.value if (guildId == null) { @@ -258,7 +258,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? { */ public suspend fun memberFor(event: Event): MemberBehavior? { return when { - event is InteractionCreateEvent -> (event.interaction as? GuildInteraction)?.member + event is ApplicationCreateEvent -> (event.interaction as? GuildApplicationCommandInteraction)?.member event is MemberJoinEvent -> event.member event is MemberUpdateEvent -> event.member @@ -399,7 +399,7 @@ public suspend fun userFor(event: Event): UserBehavior? { is DMChannelCreateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelDeleteEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelUpdateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } - is InteractionCreateEvent -> event.interaction.user + is ApplicationCreateEvent -> event.interaction.user is MemberJoinEvent -> event.member is MemberLeaveEvent -> event.user is MemberUpdateEvent -> event.member diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt index 78b3125564..d1d8f7b82e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt @@ -37,7 +37,7 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.CommandInteraction import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import io.sentry.Sentry import io.sentry.protocol.SentryId import kotlinx.coroutines.flow.toList @@ -133,7 +133,7 @@ public open class SlashCommand( public open val subCommands: MutableList> = mutableListOf() /** @suppress **/ - public open val checkList: MutableList> = mutableListOf() + public open val checkList: MutableList> = mutableListOf() public override val parser: SlashCommandParser = SlashCommandParser() @@ -382,7 +382,7 @@ public open class SlashCommand( * * @param checks Checks to apply to this command. */ - public open fun check(vararg checks: Check) { + public open fun check(vararg checks: Check) { checks.forEach { checkList.add(it) } } @@ -391,7 +391,7 @@ public open class SlashCommand( * * @param check Check to apply to this command. */ - public open fun check(check: Check) { + public open fun check(check: Check) { checkList.add(check) } @@ -410,7 +410,7 @@ public open class SlashCommand( * * @param checks Checks to apply to this command. */ - public open fun booleanCheck(vararg checks: suspend (InteractionCreateEvent) -> Boolean) { + public open fun booleanCheck(vararg checks: suspend (ChatInputCommandInteractionCreateEvent) -> Boolean) { checks.forEach(::booleanCheck) } @@ -423,7 +423,7 @@ public open class SlashCommand( * * @param check Check to apply to this command. */ - public open fun booleanCheck(check: suspend (InteractionCreateEvent) -> Boolean) { + public open fun booleanCheck(check: suspend (ChatInputCommandInteractionCreateEvent) -> Boolean) { check { if (check(event)) { pass() @@ -436,7 +436,10 @@ public open class SlashCommand( // endregion /** Run checks with the provided [InteractionCreateEvent]. Return false if any failed, true otherwise. **/ - public open suspend fun runChecks(event: InteractionCreateEvent, sendMessage: Boolean = true): Boolean { + public open suspend fun runChecks( + event: ChatInputCommandInteractionCreateEvent, + sendMessage: Boolean = true + ): Boolean { val locale = event.getLocale() // global checks @@ -573,7 +576,7 @@ public open class SlashCommand( * * @param event The interaction creation event. */ - public open suspend fun call(event: InteractionCreateEvent) { + public open suspend fun call(event: ChatInputCommandInteractionCreateEvent) { if (event.interaction !is CommandInteraction) return val interaction = event.interaction as CommandInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt index 493b83345d..56e1b41b4f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt @@ -19,7 +19,7 @@ import dev.kord.core.entity.channel.MessageChannel import dev.kord.core.entity.interaction.CommandInteraction import dev.kord.core.entity.interaction.InteractionFollowup import dev.kord.core.entity.interaction.PublicFollowupMessage -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder @@ -34,12 +34,12 @@ import dev.kord.rest.builder.message.modify.MessageModifyBuilder @ExtensionDSL public open class SlashCommandContext( private val slashCommand: SlashCommand, - event: InteractionCreateEvent, + event: ChatInputCommandInteractionCreateEvent, commandName: String, public var interactionResponse: InteractionResponseBehavior? = null ) : CommandContext(slashCommand, event, commandName, null) { /** Event that triggered this command execution. **/ - public val event: InteractionCreateEvent get() = eventObj as InteractionCreateEvent + public val event: ChatInputCommandInteractionCreateEvent get() = eventObj as ChatInputCommandInteractionCreateEvent /** Quick access to the [CommandInteraction]. **/ public val interaction: CommandInteraction get() = event.interaction as CommandInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt index c2779dd064..289ea87690 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt @@ -13,8 +13,8 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.SlashCommands import dev.kord.core.entity.interaction.CommandInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent -import dev.kord.rest.builder.interaction.ApplicationCommandCreateBuilder +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.rest.builder.interaction.* import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList @@ -177,7 +177,7 @@ public open class SlashCommandRegistry : KoinComponent { logger.debug { "Adding/updating global slash command $translatedName" } - command( + input( translatedName, translationsProvider.translate(it.description, it.extension.bundle, locale = locale) ) { register(it) } @@ -202,7 +202,7 @@ public open class SlashCommandRegistry : KoinComponent { logger.debug { "Adding/updating global slash command $translatedName" } - command( + input( translatedName, translationsProvider.translate(it.description, it.extension.bundle) ) { register(it) } @@ -254,7 +254,7 @@ public open class SlashCommandRegistry : KoinComponent { } } - internal open suspend fun ApplicationCommandCreateBuilder.register(command: SlashCommand) { + internal open suspend fun ChatInputCreateBuilder.register(command: SlashCommand) { val locale = bot.settings.i18nBuilder.defaultLocale this.defaultPermission = command.guild == null || command.allowByDefault @@ -332,7 +332,7 @@ public open class SlashCommandRegistry : KoinComponent { } /** Handle an [InteractionCreateEvent] and try to execute the corresponding command. **/ - public open suspend fun handle(event: InteractionCreateEvent) { + public open suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { val interaction = event.interaction as? CommandInteraction ?: return val commandId = interaction.command.rootId diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt index 1f4409d9c3..01e8f6d3c4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt @@ -10,7 +10,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.create.actionRow import dev.kord.rest.builder.message.modify.MessageModifyBuilder @@ -49,7 +49,7 @@ public open class Components( public val kord: Kord by inject() /** Current event handler instance waiting for interaction creation events. **/ - public var eventHandler: EventHandler? = null + public var eventHandler: EventHandler? = null /** @suppress Internal Job object representing the timeout job. **/ public var delayJob: Job? = null @@ -234,7 +234,7 @@ public open class Components( } action { - val interaction = event.interaction as ComponentInteraction + val interaction = event.interaction val component = actionableComponents[interaction.componentId]!! component.call(this@Components, extension, event, parentContext) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt index 35dc941d1a..c7352bcc67 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt @@ -24,7 +24,7 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import io.sentry.Sentry import io.sentry.protocol.SentryId import mu.KotlinLogging @@ -71,7 +71,7 @@ public abstract class ActionableComponentBuilder> = mutableListOf() + public open val checks: MutableList> = mutableListOf() /** @suppress Internal variable, the click action to run. **/ public open lateinit var body: suspend R.() -> Unit @@ -83,11 +83,11 @@ public abstract class ActionableComponentBuilder): Boolean = + public open fun check(vararg checks: Check): Boolean = this.checks.addAll(checks) /** Register a check that must pass for this button to be actioned. **/ - public open fun check(check: Check): Boolean = + public open fun check(check: Check): Boolean = checks.add(check) /** @@ -97,7 +97,7 @@ public abstract class ActionableComponentBuilder Boolean) { + public open fun booleanCheck(vararg checks: suspend (ComponentCreateEvent) -> Boolean) { checks.forEach(::booleanCheck) } @@ -108,7 +108,7 @@ public abstract class ActionableComponentBuilder Boolean) { + public open fun booleanCheck(check: suspend (ComponentCreateEvent) -> Boolean) { check { if (check(event)) { pass() @@ -124,7 +124,7 @@ public abstract class ActionableComponentBuilder? ) { if (!runChecks(event)) { @@ -280,7 +280,7 @@ public abstract class ActionableComponentBuilder? = null ) { throw UnsupportedOperationException("This type of component doesn't support callable actions.") diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt index cc4a74c1bf..d4ee357673 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt @@ -10,7 +10,7 @@ import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.DiscordPartialEmoji import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.entity.interaction.ButtonInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder import mu.KotlinLogging @@ -52,7 +52,7 @@ public open class InteractiveButtonBuilder : ButtonBuilder, override fun getContext( extension: Extension, - event: InteractionCreateEvent, + event: ComponentCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior?, interaction: ButtonInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt index df054c8ccc..860ed51c14 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.component.SelectOptionBuilder @@ -91,7 +91,7 @@ public open class MenuBuilder : ActionableComponentBuilder( public open val extension: Extension, - public open val event: InteractionCreateEvent, + public open val event: ComponentCreateEvent, public open val components: Components, public open var interactionResponse: InteractionResponseBehavior? = null, public open val interaction: T = event.interaction as T diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt index d5463e8a48..33e28b9ca3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt @@ -6,9 +6,8 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.* -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import org.koin.core.component.KoinComponent -import java.util.* /** * Context object representing the execution context of an interactive button interaction. @@ -17,7 +16,7 @@ import java.util.* @ExtensionDSL public open class InteractiveButtonContext( extension: Extension, - event: InteractionCreateEvent, + event: ComponentCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, interaction: ButtonInteraction = event.interaction as ButtonInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt index f2c9fbb33a..75253905a2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import org.koin.core.component.KoinComponent import java.util.* @@ -17,7 +17,7 @@ import java.util.* @ExtensionDSL public open class MenuContext( extension: Extension, - event: InteractionCreateEvent, + event: ComponentCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, interaction: SelectMenuInteraction = event.interaction as SelectMenuInteraction 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 47730f8642..dd4dd93af0 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 @@ -16,7 +16,7 @@ import com.kotlindiscord.kord.extensions.events.ExtensionStateEvent import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.event.Event -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import mu.KotlinLogging import org.koin.core.component.KoinComponent @@ -96,7 +96,7 @@ public abstract class Extension : KoinComponent { * * These checks will be checked against all slash commands in this extension. */ - public open val slashCommandChecks: MutableList> = + public open val slashCommandChecks: MutableList> = mutableListOf() /** String representing the bundle to get translations from for command names/descriptions. **/ @@ -386,7 +386,7 @@ public abstract class Extension : KoinComponent { * * @param checks Checks to apply to all slash commands in this extension. */ - public open fun slashCheck(vararg checks: Check) { + public open fun slashCheck(vararg checks: Check) { checks.forEach { slashCommandChecks.add(it) } } @@ -396,7 +396,7 @@ public abstract class Extension : KoinComponent { * @param check Check to apply to all slash commands in this extension. */ @ExtensionDSL - public open fun slashCheck(check: Check) { + public open fun slashCheck(check: Check) { slashCommandChecks.add(check) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index 384a3ba008..8b737d0d99 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -13,7 +13,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ButtonStyle import dev.kord.core.entity.ReactionEmoji import dev.kord.core.entity.User -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import java.util.* /** Last row number. **/ @@ -61,7 +61,7 @@ public abstract class BaseButtonPaginator( public val canUseSwitchingButtons: Boolean = allGroups.size in 3..5 && "" !in allGroups /** A button-oriented check function that matches based on the [owner] property. **/ - public val defaultCheck: Check = { + public val defaultCheck: Check = { if (!active) { fail() } else if (owner == null) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt index 6c51b7bfa8..7ea0ea3fc0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt @@ -4,7 +4,8 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot import dev.kord.common.annotation.KordPreview import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.event.Event -import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.ApplicationCreateEvent +import dev.kord.core.event.interaction.ComponentCreateEvent import dev.kord.core.event.message.MessageCreateEvent import java.util.* @@ -35,9 +36,43 @@ public suspend fun MessageCreateEvent.getLocale(): Locale { return result } -/** Attempt to resolve the locale for the given [InteractionCreateEvent] object. **/ +/** Attempt to resolve the locale for the given [ApplicationCreateEvent] object. **/ @OptIn(KordPreview::class) -public suspend fun InteractionCreateEvent.getLocale(): Locale { +public suspend fun ApplicationCreateEvent.getLocale(): Locale { + val existing = localeCache[this] + + if (existing != null) { + return existing + } + + val bot = getKoin().get() + var result = bot.settings.i18nBuilder.defaultLocale + + for (resolver in bot.settings.i18nBuilder.localeResolvers) { + val channel = interaction.channel.asChannel() + + val guild = if (channel is GuildChannel) { + channel.guild + } else { + null + } + + val resolved = resolver(guild, interaction.channel, interaction.user) + + if (resolved != null) { + result = resolved + break + } + } + + localeCache[this] = result + + return result +} + +/** Attempt to resolve the locale for the given [ComponentCreateEvent] object. **/ +@OptIn(KordPreview::class) +public suspend fun ComponentCreateEvent.getLocale(): Locale { val existing = localeCache[this] if (existing != null) { diff --git a/libs.versions.toml b/libs.versions.toml index e5268d786d..2bc878262e 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -11,7 +11,7 @@ junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" #kord = "0.8.0-M4" -kord = "0.8.x-SNAPSHOT" +kord = "contexts-SNAPSHOT" kotlinpoet = "1.8.0" ksp = "1.5.10-1.0.0-beta02" kx-ser = "1.2.1" From c0ee88470531bfa11bd2bea98d03877a8d619060 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 21 Aug 2021 23:04:30 +0100 Subject: [PATCH 003/131] Initial renaming of message commands and functions --- .../extra/mappings/MappingsExtension.kt | 56 ++++++++-------- .../kord/extensions/Exceptions.kt | 14 ++-- .../kord/extensions/ExtensibleBot.kt | 60 +++++------------ .../builders/ExtensibleBotBuilder.kt | 12 ++-- .../MessageContentCommand.kt} | 19 +++--- .../MessageContentCommandContext.kt} | 9 +-- .../MessageContentCommandRegistry.kt} | 18 ++--- .../MessageContentGroupCommand.kt} | 65 ++++++++++--------- .../MessageContentSubCommand.kt} | 10 +-- .../kord/extensions/extensions/Extension.kt | 58 +++++++++-------- .../extensions/base/HelpProvider.kt | 34 +++++----- .../extensions/impl/HelpExtension.kt | 36 +++++----- .../extensions/impl/SentryExtension.kt | 2 +- .../kord/extensions/test/bot/TestExtension.kt | 28 ++++---- .../kord/extensions/test/bot/TestExtension.kt | 2 +- .../kord/extensions/test/bot/TestExtension.kt | 2 +- 16 files changed, 208 insertions(+), 217 deletions(-) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{MessageCommand.kt => content/MessageContentCommand.kt} (95%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{MessageCommandContext.kt => content/MessageContentCommandContext.kt} (94%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{MessageCommandRegistry.kt => content/MessageContentCommandRegistry.kt} (92%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{MessageGroupCommand.kt => content/MessageContentGroupCommand.kt} (73%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{MessageSubCommand.kt => content/MessageContentSubCommand.kt} (77%) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index f742f2375c..fc6b8ec7ec 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -4,7 +4,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings import com.kotlindiscord.kord.extensions.checks.and import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.MessageCommandContext +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandContext import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments.* @@ -85,7 +85,7 @@ class MappingsExtension : Extension() { if (legacyYarnEnabled) { // Class - command(::LegacyYarnArguments) { + messageContentCommand(::LegacyYarnArguments) { name = "lyc" aliases = arrayOf("lyarnc", "legacy-yarnc", "legacyyarnc", "legacyarnc") @@ -103,7 +103,7 @@ class MappingsExtension : Extension() { } // Field - command(::LegacyYarnArguments) { + messageContentCommand(::LegacyYarnArguments) { name = "lyf" aliases = arrayOf("lyarnf", "legacy-yarnf", "legacyyarnf", "legacyarnf") @@ -121,7 +121,7 @@ class MappingsExtension : Extension() { } // Method - command(::LegacyYarnArguments) { + messageContentCommand(::LegacyYarnArguments) { name = "lym" aliases = arrayOf("lyarnm", "legacy-yarnm", "legacyyarnm", "legacyarnm") @@ -145,7 +145,7 @@ class MappingsExtension : Extension() { if (mcpEnabled) { // Class - command(::MCPArguments) { + messageContentCommand(::MCPArguments) { name = "mcpc" description = "Look up MCP mappings info for a class.\n\n" + @@ -161,7 +161,7 @@ class MappingsExtension : Extension() { } // Field - command(::MCPArguments) { + messageContentCommand(::MCPArguments) { name = "mcpf" description = "Look up MCP mappings info for a field.\n\n" + @@ -177,7 +177,7 @@ class MappingsExtension : Extension() { } // Method - command(::MCPArguments) { + messageContentCommand(::MCPArguments) { name = "mcpm" description = "Look up MCP mappings info for a method.\n\n" + @@ -199,7 +199,7 @@ class MappingsExtension : Extension() { if (mojangEnabled) { // Class - command(::MojangArguments) { + messageContentCommand(::MojangArguments) { name = "mmc" aliases = arrayOf("mojc", "mojmapc") @@ -220,7 +220,7 @@ class MappingsExtension : Extension() { } // Field - command(::MojangArguments) { + messageContentCommand(::MojangArguments) { name = "mmf" aliases = arrayOf("mojf", "mojmapf") @@ -241,7 +241,7 @@ class MappingsExtension : Extension() { } // Method - command(::MojangArguments) { + messageContentCommand(::MojangArguments) { name = "mmm" aliases = arrayOf("mojm", "mojmapm") @@ -268,7 +268,7 @@ class MappingsExtension : Extension() { if (plasmaEnabled) { // Class - command(::PlasmaArguments) { + messageContentCommand(::PlasmaArguments) { name = "pc" description = "Look up Plasma mappings info for a class.\n\n" + @@ -285,7 +285,7 @@ class MappingsExtension : Extension() { } // Field - command(::PlasmaArguments) { + messageContentCommand(::PlasmaArguments) { name = "pf" description = "Look up Plasma mappings info for a field.\n\n" + @@ -302,7 +302,7 @@ class MappingsExtension : Extension() { } // Method - command(::PlasmaArguments) { + messageContentCommand(::PlasmaArguments) { name = "pm" description = "Look up Plasma mappings info for a method.\n\n" + @@ -325,7 +325,7 @@ class MappingsExtension : Extension() { if (yarnEnabled) { // Class - command({ YarnArguments(patchworkEnabled) }) { + messageContentCommand({ YarnArguments(patchworkEnabled) }) { name = "yc" aliases = arrayOf("yarnc") @@ -351,7 +351,7 @@ class MappingsExtension : Extension() { } // Field - command({ YarnArguments(patchworkEnabled) }) { + messageContentCommand({ YarnArguments(patchworkEnabled) }) { name = "yf" aliases = arrayOf("yarnf") @@ -377,7 +377,7 @@ class MappingsExtension : Extension() { } // Method - command({ YarnArguments(patchworkEnabled) }) { + messageContentCommand({ YarnArguments(patchworkEnabled) }) { name = "ym" aliases = arrayOf("yarnm") @@ -409,7 +409,7 @@ class MappingsExtension : Extension() { if (yarrnEnabled) { // Class - command(::YarrnArguments) { + messageContentCommand(::YarrnArguments) { name = "yrc" description = "Look up Yarrn mappings info for a class.\n\n" + @@ -426,7 +426,7 @@ class MappingsExtension : Extension() { } // Field - command(::YarrnArguments) { + messageContentCommand(::YarrnArguments) { name = "yrf" description = "Look up Yarrn mappings info for a field.\n\n" + @@ -443,7 +443,7 @@ class MappingsExtension : Extension() { } // Method - command(::YarrnArguments) { + messageContentCommand(::YarrnArguments) { name = "yrm" description = "Look up Yarrn mappings info for a method.\n\n" + @@ -465,7 +465,7 @@ class MappingsExtension : Extension() { // region: Mappings info commands if (legacyYarnEnabled) { - command { + messageContentCommand { name = "lyarn" aliases = arrayOf("legacy-yarn", "legacyyarn", "legacyarn") @@ -532,7 +532,7 @@ class MappingsExtension : Extension() { } if (mcpEnabled) { - command { + messageContentCommand { name = "mcp" description = "Get information and a list of supported versions for MCP mappings." @@ -597,7 +597,7 @@ class MappingsExtension : Extension() { } if (mojangEnabled) { - command { + messageContentCommand { name = "mojang" aliases = arrayOf("mojmap") @@ -666,7 +666,7 @@ class MappingsExtension : Extension() { } if (plasmaEnabled) { - command { + messageContentCommand { name = "plasma" description = "Get information and a list of supported versions for Plasma mappings." @@ -732,7 +732,7 @@ class MappingsExtension : Extension() { } if (yarnEnabled) { - command { + messageContentCommand { name = "yarn" description = "Get information and a list of supported versions for Yarn mappings." @@ -826,7 +826,7 @@ class MappingsExtension : Extension() { } if (yarrnEnabled) { - command { + messageContentCommand { name = "yarrn" description = "Get information and a list of supported versions for Yarrn mappings." @@ -896,7 +896,7 @@ class MappingsExtension : Extension() { logger.info { "Mappings extension set up - namespaces: " + enabledNamespaces.joinToString(", ") } } - private suspend fun MessageCommandContext.queryClasses( + private suspend fun MessageContentCommandContext.queryClasses( namespace: Namespace, givenQuery: String, version: MappingsContainer?, @@ -1005,7 +1005,7 @@ class MappingsExtension : Extension() { paginator.send() } - private suspend fun MessageCommandContext.queryFields( + private suspend fun MessageContentCommandContext.queryFields( namespace: Namespace, givenQuery: String, version: MappingsContainer?, @@ -1114,7 +1114,7 @@ class MappingsExtension : Extension() { paginator.send() } - private suspend fun MessageCommandContext.queryMethods( + private suspend fun MessageContentCommandContext.queryMethods( namespace: Namespace, givenQuery: String, version: MappingsContainer?, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt index f4c25bb7e8..feeb43bf5a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions -import com.kotlindiscord.kord.extensions.commands.MessageCommand +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.extensions.Extension import kotlin.reflect.KClass @@ -50,10 +50,10 @@ public class EventHandlerRegistrationException(public val reason: String) : Exte } /** - * Thrown when a [MessageCommand] could not be validated. + * Thrown when a [MessageContentCommand] could not be validated. * - * @param name The [MessageCommand] name - * @param reason Why this [MessageCommand] is considered invalid. + * @param name The [MessageContentCommand] name + * @param reason Why this [MessageContentCommand] is considered invalid. */ public class InvalidCommandException(public val name: String?, public val reason: String) : ExtensionsException() { override fun toString(): String { @@ -66,10 +66,10 @@ public class InvalidCommandException(public val name: String?, public val reason } /** - * Thrown when an attempt to register a [MessageCommand] fails. + * Thrown when an attempt to register a [MessageContentCommand] fails. * - * @param name The [MessageCommand] name - * @param reason Why this [MessageCommand] could not be registered. + * @param name The [MessageContentCommand] name + * @param reason Why this [MessageContentCommand] could not be registered. */ public class CommandRegistrationException(public val name: String?, public val reason: String) : ExtensionsException() { override fun toString(): String { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 6bf741f09a..920f19a9cc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.MessageCommand -import com.kotlindiscord.kord.extensions.commands.MessageCommandRegistry +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler @@ -65,34 +65,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva ) public val kord: Kord by inject() - /** Message command registry, keeps track of and executes message commands. **/ - @Deprecated( - "Use Koin to get this instead. This will be made private in future.", - - ReplaceWith( - "getKoin().get()", - - "com.kotlindiscord.kord.extensions.utils.getKoin", - "com.kotlindiscord.kord.extensions.commands.MessageCommandRegistry" - ), - level = DeprecationLevel.ERROR - ) - public open val messageCommands: MessageCommandRegistry by inject() - - /** Slash command registry, keeps track of and executes slash commands. **/ - @Deprecated( - "Use Koin to get this instead. This will be made private in future.", - - ReplaceWith( - "getKoin().get()", - - "com.kotlindiscord.kord.extensions.utils.getKoin", - "com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry" - ), - level = DeprecationLevel.ERROR - ) - public open val slashCommands: SlashCommandRegistry by inject() - /** * A list of all registered event handlers. */ @@ -197,7 +169,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva if (settings.messageCommandsBuilder.enabled) { on { - getKoin().get().handleEvent(this) + getKoin().get().handleEvent(this) } } @@ -336,10 +308,10 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } /** - * Directly register a [MessageCommand] to this bot. + * Directly register a [MessageContentCommand] to this bot. * * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and - * call the [Extension.command] function in your [Extension.setup] function. + * call the [Extension.messageContentCommand] function in your [Extension.setup] function. * * This function will throw a [CommandRegistrationException] if the command has already been registered, if * a command with the same name exists, or if a command with one of the same aliases exists. @@ -348,23 +320,23 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva * @throws CommandRegistrationException Thrown if the command could not be registered. */ @Deprecated( - "Use the equivalent function within `MessageCommandRegistry` instead.", + "Use the equivalent function within `MessageContentCommandRegistry` instead.", ReplaceWith( - "getKoin().get().add(command)", + "getKoin().get().add(command)", "org.koin.core.component.KoinComponent.getKoin", - "com.kotlindiscord.kord.extensions.commands.MessageCommand" + "com.kotlindiscord.kord.extensions.commands.MessageContentCommand" ), level = DeprecationLevel.ERROR ) @Throws(CommandRegistrationException::class) - public open fun addCommand(command: MessageCommand): Unit = getKoin() - .get() + public open fun addCommand(command: MessageContentCommand): Unit = getKoin() + .get() .add(command) /** - * Directly remove a registered [MessageCommand] from this bot. + * Directly remove a registered [MessageContentCommand] from this bot. * * This function is used when extensions are unloaded, in order to clear out their commands. * No exception is thrown if the command wasn't registered. @@ -372,18 +344,18 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva * @param command The command to be removed. */ @Deprecated( - "Use the equivalent function within `MessageCommandRegistry` instead.", + "Use the equivalent function within `MessageContentCommandRegistry` instead.", ReplaceWith( - "getKoin().get().remove(command)", + "getKoin().get().remove(command)", "org.koin.core.component.KoinComponent.getKoin", - "com.kotlindiscord.kord.extensions.commands.MessageCommand" + "com.kotlindiscord.kord.extensions.commands.MessageContentCommand" ), level = DeprecationLevel.ERROR ) - public open fun removeCommand(command: MessageCommand): Boolean = getKoin() - .get() + public open fun removeCommand(command: MessageContentCommand): Boolean = getKoin() + .get() .remove(command) /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 5a39c9e6b9..30385126e7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.BotBuilderDSL import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.MessageCommandRegistry +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations @@ -225,7 +225,7 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } - loadModule { single { messageCommandsBuilder.registryBuilder() } bind MessageCommandRegistry::class } + loadModule { single { messageCommandsBuilder.registryBuilder() } bind MessageContentCommandRegistry::class } loadModule { single { slashCommandsBuilder.slashRegistryBuilder() } bind SlashCommandRegistry::class } loadModule { @@ -802,7 +802,7 @@ public open class ExtensibleBotBuilder { public var prefixCallback: suspend (MessageCreateEvent).(String) -> String = { defaultPrefix } /** @suppress Builder that shouldn't be set directly by the user. **/ - public var registryBuilder: () -> MessageCommandRegistry = { MessageCommandRegistry() } + public var registryBuilder: () -> MessageContentCommandRegistry = { MessageContentCommandRegistry() } /** * List of command checks. @@ -823,10 +823,10 @@ public open class ExtensibleBotBuilder { } /** - * Register the builder used to create the [MessageCommandRegistry]. You can change this if you need to make - * use of a subclass. + * Register the builder used to create the [MessageContentCommandRegistry]. You can change this if you need to + * make use of a subclass. */ - public fun registry(builder: () -> MessageCommandRegistry) { + public fun registry(builder: () -> MessageContentCommandRegistry) { registryBuilder = builder } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt similarity index 95% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt index 7c43ea8669..2f9a2a5a90 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt @@ -1,12 +1,13 @@ @file:Suppress("StringLiteralDuplication") -package com.kotlindiscord.kord.extensions.commands +package com.kotlindiscord.kord.extensions.commands.content import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.commands.Command import com.kotlindiscord.kord.extensions.commands.parser.ArgumentParser import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension @@ -39,14 +40,14 @@ private val logger = KotlinLogging.logger {} * Class representing a message command. * * You shouldn't need to use this class directly - instead, create an [Extension] and use the - * [command function][Extension.command] to register your command, by overriding the [Extension.setup] + * [command function][Extension.messageContentCommand] to register your command, by overriding the [Extension.setup] * function. * * @param extension The [Extension] that registered this command. * @param arguments Arguments object builder for this command, if it has arguments. */ @ExtensionDSL -public open class MessageCommand( +public open class MessageContentCommand( extension: Extension, public open val arguments: (() -> T)? = null ) : Command(extension), KoinComponent { @@ -54,7 +55,7 @@ public open class MessageCommand( public val translationsProvider: TranslationsProvider by inject() /** Message command registry. **/ - public val messageCommandRegistry: MessageCommandRegistry by inject() + public val messageCommandRegistry: MessageContentCommandRegistry by inject() /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() @@ -65,7 +66,7 @@ public open class MessageCommand( /** * @suppress */ - public open lateinit var body: suspend MessageCommandContext.() -> Unit + public open lateinit var body: suspend MessageContentCommandContext.() -> Unit /** * A description of what this function and how it's intended to be used. @@ -224,7 +225,7 @@ public open class MessageCommand( * * @param action The body of your command, which will be executed when your command is invoked. */ - public open fun action(action: suspend MessageCommandContext.() -> Unit) { + public open fun action(action: suspend MessageContentCommandContext.() -> Unit) { this.body = action } @@ -390,7 +391,7 @@ public open class MessageCommand( return } - val context = MessageCommandContext(this, event, commandName, parser, argString) + val context = MessageContentCommandContext(this, event, commandName, parser, argString) context.populate() @@ -463,8 +464,8 @@ public open class MessageCommand( val channel = event.message.getChannelOrNull() val translatedName = when (this) { - is MessageSubCommand -> this.getFullTranslatedName(context.getLocale()) - is GroupCommand -> this.getFullTranslatedName(context.getLocale()) + is MessageContentSubCommand -> this.getFullTranslatedName(context.getLocale()) + is MessageContentGroupCommand -> this.getFullTranslatedName(context.getLocale()) else -> this.getTranslatedName(context.getLocale()) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt similarity index 94% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt index eda74434d1..d85d6ec30f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt @@ -1,8 +1,9 @@ @file:OptIn(KordPreview::class) -package com.kotlindiscord.kord.extensions.commands +package com.kotlindiscord.kord.extensions.commands.content import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider @@ -23,12 +24,12 @@ import dev.kord.rest.builder.message.modify.MessageModifyBuilder /** * Command context object representing the context given to message commands. * - * @property messageCommand Message command object, typed as [MessageCommand] rather than [Command] + * @property messageCommand Message command object, typed as [MessageContentCommand] rather than [Command] * @property argString String containing the command's unparsed arguments, raw, fresh from Discord itself. */ @ExtensionDSL -public open class MessageCommandContext( - public val messageCommand: MessageCommand, +public open class MessageContentCommandContext( + public val messageCommand: MessageContentCommand, eventObj: MessageCreateEvent, commandName: String, parser: StringParser, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt similarity index 92% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandRegistry.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt index 15125b9499..2f06a13891 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands +package com.kotlindiscord.kord.extensions.commands.content import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.ExtensibleBot @@ -21,7 +21,7 @@ import java.util.concurrent.Executors * A class for the registration and dispatching of message-based commands. */ @OptIn(KordPreview::class) -public open class MessageCommandRegistry : KoinComponent { +public open class MessageContentCommandRegistry : KoinComponent { /** Current instance of the bot. **/ public val bot: ExtensibleBot by inject() @@ -31,7 +31,7 @@ public open class MessageCommandRegistry : KoinComponent { /** * A list of all registered commands. */ - public open val commands: MutableList> = mutableListOf() + public open val commands: MutableList> = mutableListOf() /** @suppress **/ public val botSettings: ExtensibleBotBuilder by inject() @@ -44,10 +44,10 @@ public open class MessageCommandRegistry : KoinComponent { } /** - * Directly register a [MessageCommand] to this command registry. + * Directly register a [MessageContentCommand] to this command registry. * * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and - * call the [Extension.command] function in your [Extension.setup] function. + * call the [Extension.messageContentCommand] function in your [Extension.setup] function. * * This function will throw a [CommandRegistrationException] if the command has already been registered, if * a command with the same name exists, or if a command with one of the same aliases exists. @@ -56,7 +56,7 @@ public open class MessageCommandRegistry : KoinComponent { * @throws CommandRegistrationException Thrown if the command could not be registered. */ @Throws(CommandRegistrationException::class) - public open fun add(command: MessageCommand) { + public open fun add(command: MessageContentCommand) { val existingCommand = commands.any { it.name == command.name } val existingAlias: String? = commands.flatMap { it.aliases.toList() @@ -88,14 +88,14 @@ public open class MessageCommandRegistry : KoinComponent { } /** - * Directly remove a registered [MessageCommand] from this command registry. + * Directly remove a registered [MessageContentCommand] from this command registry. * * This function is used when extensions are unloaded, in order to clear out their commands. * No exception is thrown if the command wasn't registered. * * @param command The command to be removed. */ - public open fun remove(command: MessageCommand): Boolean = commands.remove(command) + public open fun remove(command: MessageContentCommand): Boolean = commands.remove(command) /** * Given a [MessageCreateEvent], return the prefix that should be used for a command invocation. @@ -243,7 +243,7 @@ public open class MessageCommandRegistry : KoinComponent { * * If a command supports locale fallback, this will also attempt to resolve names via the bot's default locale. */ - public open suspend fun getCommand(name: String, event: MessageCreateEvent): MessageCommand? { + public open suspend fun getCommand(name: String, event: MessageCreateEvent): MessageContentCommand? { val defaultLocale = botSettings.i18nBuilder.defaultLocale val locale = event.getLocale() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt similarity index 73% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageGroupCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt index 36f572f03e..446cdc86c6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands +package com.kotlindiscord.kord.extensions.commands.content import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException @@ -22,26 +22,26 @@ private val logger = KotlinLogging.logger {} * `group` function to register your command group, by overriding the `Extension` setup function. * * @param extension The extension that registered this grouped command. - * @param parent The [GroupCommand] this group exists under, if any. + * @param parent The [MessageContentGroupCommand] this group exists under, if any. */ @Suppress("LateinitVarOverridesLateinitVar") // This is intentional @ExtensionDSL -public open class GroupCommand( +public open class MessageContentGroupCommand( extension: Extension, arguments: (() -> T)? = null, - public open val parent: GroupCommand? = null -) : MessageCommand(extension, arguments) { + public open val parent: MessageContentGroupCommand? = null +) : MessageContentCommand(extension, arguments) { /** @suppress **/ public val botSettings: ExtensibleBotBuilder by inject() /** @suppress **/ - public open val commands: MutableList> = mutableListOf() + public open val commands: MutableList> = mutableListOf() override lateinit var name: String /** @suppress **/ - override var body: suspend MessageCommandContext.() -> Unit = { + override var body: suspend MessageContentCommandContext.() -> Unit = { sendHelp() } @@ -68,14 +68,14 @@ public open class GroupCommand( * * @param body Builder lambda used for setting up the command object. */ - public open suspend fun command( + public open suspend fun messageContentCommand( arguments: (() -> R)?, - body: suspend MessageCommand.() -> Unit - ): MessageCommand { - val commandObj = MessageSubCommand(extension, arguments, this) + body: suspend MessageContentCommand.() -> Unit + ): MessageContentCommand { + val commandObj = MessageContentSubCommand(extension, arguments, this) body.invoke(commandObj) - return command(commandObj) + return messageContentCommand(commandObj) } /** @@ -85,13 +85,13 @@ public open class GroupCommand( * * @param body Builder lambda used for setting up the command object. */ - public open suspend fun command( - body: suspend MessageCommand.() -> Unit - ): MessageCommand { - val commandObj = MessageSubCommand(extension, parent = this) + public open suspend fun messageContentCommand( + body: suspend MessageContentCommand.() -> Unit + ): MessageContentCommand { + val commandObj = MessageContentSubCommand(extension, parent = this) body.invoke(commandObj) - return command(commandObj) + return messageContentCommand(commandObj) } /** @@ -101,7 +101,9 @@ public open class GroupCommand( * * @param commandObj MessageCommand object to register. */ - public open suspend fun command(commandObj: MessageCommand): MessageCommand { + public open suspend fun messageContentCommand( + commandObj: MessageContentCommand + ): MessageContentCommand { try { commandObj.validate() commands.add(commandObj) @@ -124,14 +126,15 @@ public open class GroupCommand( * * @param body Builder lambda used for setting up the command object. */ - public open suspend fun group( + @Suppress("MemberNameEqualsClassName") // Really? + public open suspend fun messageContentGroupCommand( arguments: (() -> R)?, - body: suspend GroupCommand.() -> Unit - ): GroupCommand { - val commandObj = GroupCommand(extension, arguments, this) + body: suspend MessageContentGroupCommand.() -> Unit + ): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(extension, arguments, this) body.invoke(commandObj) - return command(commandObj) as GroupCommand + return messageContentCommand(commandObj) as MessageContentGroupCommand } /** @@ -144,17 +147,21 @@ public open class GroupCommand( * * @param body Builder lambda used for setting up the command object. */ - public open suspend fun group( - body: suspend GroupCommand.() -> Unit - ): GroupCommand { - val commandObj = GroupCommand(extension, parent = this) + @Suppress("MemberNameEqualsClassName") // Really? + public open suspend fun messageContentGroupCommand( + body: suspend MessageContentGroupCommand.() -> Unit + ): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(extension, parent = this) body.invoke(commandObj) - return command(commandObj) as GroupCommand + return messageContentCommand(commandObj) as MessageContentGroupCommand } /** @suppress **/ - public open suspend fun getCommand(name: String?, event: MessageCreateEvent): MessageCommand? { + public open suspend fun getCommand( + name: String?, + event: MessageCreateEvent + ): MessageContentCommand? { name ?: return null val defaultLocale = botSettings.i18nBuilder.defaultLocale diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageSubCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt similarity index 77% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageSubCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt index b1b18b6352..df357cc092 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/MessageSubCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands +package com.kotlindiscord.kord.extensions.commands.content import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.parser.Arguments @@ -11,14 +11,14 @@ import java.util.* * This is used for group commands, so that subcommands are aware of their parent. * * @param extension The [Extension] that registered this command. - * @param parent The [GroupCommand] this command exists under. + * @param parent The [MessageContentGroupCommand] this command exists under. */ @ExtensionDSL -public open class MessageSubCommand( +public open class MessageContentSubCommand( extension: Extension, arguments: (() -> T)? = null, - public open val parent: GroupCommand -) : MessageCommand(extension, arguments) { + public open val parent: MessageContentGroupCommand +) : MessageContentCommand(extension, arguments) { /** Get the full command name, translated, with parent commands taken into account. **/ public open suspend fun getFullTranslatedName(locale: Locale): String = parent.getFullTranslatedName(locale) + " " + this.getTranslatedName(locale) 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 dd4dd93af0..52dea5c2a3 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 @@ -5,9 +5,9 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.* import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.GroupCommand -import com.kotlindiscord.kord.extensions.commands.MessageCommand -import com.kotlindiscord.kord.extensions.commands.MessageCommandRegistry +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry +import com.kotlindiscord.kord.extensions.commands.content.MessageContentGroupCommand import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry @@ -39,7 +39,7 @@ public abstract class Extension : KoinComponent { public open val kord: Kord by inject() /** Message command registry. **/ - private val messageCommandsRegistry: MessageCommandRegistry by inject() + private val messageContentCommandsRegistry: MessageContentCommandRegistry by inject() /** Slash command registry. **/ private val slashCommandsRegistry: SlashCommandRegistry by inject() @@ -73,7 +73,7 @@ public abstract class Extension : KoinComponent { * * When an extension is unloaded, all the commands are removed from the bot. */ - public open val commands: MutableList> = mutableListOf() + public open val commands: MutableList> = mutableListOf() /** * List of registered slash commands. @@ -147,14 +147,14 @@ public abstract class Extension : KoinComponent { * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun command( + public open suspend fun messageContentCommand( arguments: () -> T, - body: suspend MessageCommand.() -> Unit - ): MessageCommand { - val commandObj = MessageCommand(this, arguments) + body: suspend MessageContentCommand.() -> Unit + ): MessageContentCommand { + val commandObj = MessageContentCommand(this, arguments) body.invoke(commandObj) - return command(commandObj) + return messageContentCommand(commandObj) } /** @@ -165,13 +165,13 @@ public abstract class Extension : KoinComponent { * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun command( - body: suspend MessageCommand.() -> Unit - ): MessageCommand { - val commandObj = MessageCommand(this) + public open suspend fun messageContentCommand( + body: suspend MessageContentCommand.() -> Unit + ): MessageContentCommand { + val commandObj = MessageContentCommand(this) body.invoke(commandObj) - return command(commandObj) + return messageContentCommand(commandObj) } /** @@ -179,12 +179,14 @@ public abstract class Extension : KoinComponent { * * You can use this if you have a custom command subclass you need to register. * - * @param commandObj MessageCommand object to register. + * @param commandObj MessageContentCommand object to register. */ - public open suspend fun command(commandObj: MessageCommand): MessageCommand { + public open suspend fun messageContentCommand( + commandObj: MessageContentCommand + ): MessageContentCommand { try { commandObj.validate() - messageCommandsRegistry.add(commandObj) + messageContentCommandsRegistry.add(commandObj) commands.add(commandObj) } catch (e: CommandRegistrationException) { logger.error(e) { "Failed to register command - $e" } @@ -265,14 +267,14 @@ public abstract class Extension : KoinComponent { * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun group( + public open suspend fun messageContentGroupCommand( arguments: () -> T, - body: suspend GroupCommand.() -> Unit - ): GroupCommand { - val commandObj = GroupCommand(this, arguments) + body: suspend MessageContentGroupCommand.() -> Unit + ): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(this, arguments) body.invoke(commandObj) - return command(commandObj) as GroupCommand + return messageContentCommand(commandObj) as MessageContentGroupCommand } /** @@ -286,11 +288,13 @@ public abstract class Extension : KoinComponent { * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun group(body: suspend GroupCommand.() -> Unit): GroupCommand { - val commandObj = GroupCommand(this) + public open suspend fun messageContentGroupCommand( + body: suspend MessageContentGroupCommand.() -> Unit + ): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(this) body.invoke(commandObj) - return command(commandObj) as GroupCommand + return messageContentCommand(commandObj) as MessageContentGroupCommand } /** @@ -332,7 +336,7 @@ public abstract class Extension : KoinComponent { } for (command in commands) { - messageCommandsRegistry.remove(command) + messageContentCommandsRegistry.remove(command) } eventHandlers.clear() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt index a43515a23c..f327b38504 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt @@ -1,8 +1,8 @@ package com.kotlindiscord.kord.extensions.extensions.base -import com.kotlindiscord.kord.extensions.commands.MessageCommand -import com.kotlindiscord.kord.extensions.commands.MessageCommandContext -import com.kotlindiscord.kord.extensions.commands.MessageCommandRegistry +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandContext +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.pagination.BasePaginator import com.kotlindiscord.kord.extensions.utils.getKoin @@ -34,7 +34,7 @@ public interface HelpProvider { public suspend fun formatCommandHelp( prefix: String, event: MessageCreateEvent, - command: MessageCommand, + command: MessageContentCommand, longDescription: Boolean = false ): Triple @@ -51,11 +51,11 @@ public interface HelpProvider { * description, and the command's argument list. */ public suspend fun formatCommandHelp( - context: MessageCommandContext<*>, - command: MessageCommand, + context: MessageContentCommandContext<*>, + command: MessageContentCommand, longDescription: Boolean = false ): Triple { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return formatCommandHelp(prefix, context.event, command, longDescription) } @@ -63,12 +63,12 @@ public interface HelpProvider { /** * Gather all available commands (with passing checks) from the bot, and return them. */ - public suspend fun gatherCommands(event: MessageCreateEvent): List> + public suspend fun gatherCommands(event: MessageCreateEvent): List> /** * Return the [MessageCommand] specified in the arguments, or `null` if it can't be found (or the checks fail). */ - public suspend fun getCommand(event: MessageCreateEvent, args: List): MessageCommand? + public suspend fun getCommand(event: MessageCreateEvent, args: List): MessageContentCommand? /** * Given an event, prefix and argument list, attempt to find the command represented by the arguments and return @@ -100,10 +100,10 @@ public interface HelpProvider { * @return Paginator containing the command's help, or an error message. */ public suspend fun getCommandHelpPaginator( - context: MessageCommandContext<*>, + context: MessageContentCommandContext<*>, args: List ): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return getCommandHelpPaginator(context.event, prefix, args) } @@ -126,7 +126,7 @@ public interface HelpProvider { public suspend fun getCommandHelpPaginator( event: MessageCreateEvent, prefix: String, - command: MessageCommand? + command: MessageContentCommand? ): BasePaginator /** @@ -144,10 +144,10 @@ public interface HelpProvider { * @return Paginator containing the command's help, or an error message. */ public suspend fun getCommandHelpPaginator( - context: MessageCommandContext<*>, - command: MessageCommand? + context: MessageContentCommandContext<*>, + command: MessageContentCommand? ): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return getCommandHelpPaginator(context.event, prefix, command) } @@ -183,8 +183,8 @@ public interface HelpProvider { * * @return BasePaginator containing help information for all loaded commands with passing checks. */ - public suspend fun getMainHelpPaginator(context: MessageCommandContext<*>): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + public suspend fun getMainHelpPaginator(context: MessageContentCommandContext<*>): BasePaginator { + val prefix = getKoin().get().getPrefix(context.event) return getMainHelpPaginator(context.event, prefix) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt index e2114892bd..aef244fe68 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt @@ -3,7 +3,7 @@ package com.kotlindiscord.kord.extensions.extensions.impl import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.* +import com.kotlindiscord.kord.extensions.commands.content.* import com.kotlindiscord.kord.extensions.commands.converters.impl.stringList import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension @@ -43,7 +43,7 @@ public class HelpExtension : HelpProvider, Extension() { public val translationsProvider: TranslationsProvider by inject() /** Message command registry. **/ - public val messageCommandsRegistry: MessageCommandRegistry by inject() + public val messageCommandsRegistry: MessageContentCommandRegistry by inject() /** Bot settings. **/ public val botSettings: ExtensibleBotBuilder by inject() @@ -53,7 +53,7 @@ public class HelpExtension : HelpProvider, Extension() { botSettings.extensionsBuilder.helpExtensionBuilder override suspend fun setup() { - command(::HelpArguments) { + messageContentCommand(::HelpArguments) { name = "extensions.help.commandName" aliasKey = "extensions.help.commandAliases" description = "extensions.help.commandDescription" @@ -174,13 +174,16 @@ public class HelpExtension : HelpProvider, Extension() { args: List ): BasePaginator = getCommandHelpPaginator(event, prefix, getCommand(event, args)) - override suspend fun getCommandHelpPaginator(context: MessageCommandContext<*>, args: List): BasePaginator = + override suspend fun getCommandHelpPaginator( + context: MessageContentCommandContext<*>, + args: List + ): BasePaginator = getCommandHelpPaginator(context, getCommand(context.event, args)) override suspend fun getCommandHelpPaginator( event: MessageCreateEvent, prefix: String, - command: MessageCommand? + command: MessageContentCommand? ): BasePaginator { val pages = Pages(COMMANDS_GROUP) val locale = event.getLocale() @@ -206,9 +209,9 @@ public class HelpExtension : HelpProvider, Extension() { } else { val (openingLine, desc, arguments) = formatCommandHelp(prefix, event, command, longDescription = true) - val commandName = if (command is MessageSubCommand) { + val commandName = if (command is MessageContentSubCommand) { command.getFullTranslatedName(locale) - } else if (command is GroupCommand) { + } else if (command is MessageContentGroupCommand) { command.getFullTranslatedName(locale) } else { command.getTranslatedName(locale) @@ -251,7 +254,7 @@ public class HelpExtension : HelpProvider, Extension() { } } - override suspend fun gatherCommands(event: MessageCreateEvent): List> = + override suspend fun gatherCommands(event: MessageCreateEvent): List> = messageCommandsRegistry.commands .filter { !it.hidden && it.enabled && it.runChecks(event, false) } .sortedBy { it.name } @@ -259,15 +262,15 @@ public class HelpExtension : HelpProvider, Extension() { override suspend fun formatCommandHelp( prefix: String, event: MessageCreateEvent, - command: MessageCommand, + command: MessageContentCommand, longDescription: Boolean ): Triple { val locale = event.getLocale() val defaultLocale = botSettings.i18nBuilder.defaultLocale - val commandName = if (command is MessageSubCommand) { + val commandName = if (command is MessageContentSubCommand) { command.getFullTranslatedName(locale) - } else if (command is GroupCommand) { + } else if (command is MessageContentGroupCommand) { command.getFullTranslatedName(locale) } else { command.getTranslatedName(locale) @@ -307,7 +310,7 @@ public class HelpExtension : HelpProvider, Extension() { } } - if (command is GroupCommand) { + if (command is MessageContentGroupCommand) { val subCommands = command.commands.filter { it.runChecks(event, false) } if (subCommands.isNotEmpty()) { @@ -379,7 +382,10 @@ public class HelpExtension : HelpProvider, Extension() { return Triple(openingLine.trim('\n'), description.trim('\n'), arguments.trim('\n')) } - override suspend fun getCommand(event: MessageCreateEvent, args: List): MessageCommand? { + override suspend fun getCommand( + event: MessageCreateEvent, + args: List + ): MessageContentCommand? { val firstArg = args.first() var command = messageCommandsRegistry.getCommand(firstArg, event) @@ -388,8 +394,8 @@ public class HelpExtension : HelpProvider, Extension() { } args.drop(1).forEach { - if (command is GroupCommand) { - val gc = command as GroupCommand + if (command is MessageContentGroupCommand) { + val gc = command as MessageContentGroupCommand command = if (gc.runChecks(event, false)) { gc.getCommand(it, event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index b755d52fd0..ab85e82eec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -67,7 +67,7 @@ public class SentryExtension : Extension() { } } - command(::FeedbackMessageArgs) { + messageContentCommand(::FeedbackMessageArgs) { name = "extensions.sentry.commandName" description = "extensions.sentry.commandDescription.long" diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 4f243dd1cd..0a6092ad0f 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -70,7 +70,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - command(::ColorArgs) { + messageContentCommand(::ColorArgs) { name = "color" aliases = arrayOf("colour") description = "Get an embed with a set color" @@ -86,7 +86,7 @@ class TestExtension : Extension() { } } - command(::MessageArgs) { + messageContentCommand(::MessageArgs) { name = "msg" description = "Message argument test" @@ -97,7 +97,7 @@ class TestExtension : Extension() { } } - command(::CoalescedArgs) { + messageContentCommand(::CoalescedArgs) { name = "coalesce" description = "Coalesce me, baby" @@ -115,7 +115,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "dropdown" description = "Dropdown test!" @@ -434,7 +434,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "translation-test" description = "Let's test translations." @@ -444,7 +444,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "requires-perms" description = "A command that requires some permissions" @@ -455,7 +455,7 @@ class TestExtension : Extension() { } } - command(::TestArgs) { + messageContentCommand(::TestArgs) { name = "test" description = "Test command, please ignore\n\n" + @@ -496,7 +496,7 @@ class TestExtension : Extension() { } } - command(::TestArgs) { + messageContentCommand(::TestArgs) { name = "test-help" description = "Sends help for this command.\n\n" + @@ -507,7 +507,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "page" description = "Paginator test" @@ -568,7 +568,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "page2" description = "Paginator test 2" @@ -611,11 +611,11 @@ class TestExtension : Extension() { } } - group { + messageContentGroupCommand { name = "group" description = "Command group" - command { + messageContentCommand { name = "one" description = "one" @@ -624,7 +624,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "two" description = "two" @@ -633,7 +633,7 @@ class TestExtension : Extension() { } } - command { + messageContentCommand { name = "three" description = "three" diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 813b9b5a8e..db458237c2 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -22,7 +22,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - command(::TestArgs) { + messageContentCommand(::TestArgs) { name = "format" description = "Let's test formatting." diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 5ee83e3253..7160b1763d 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -22,7 +22,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - command(::TestArgs) { + messageContentCommand(::TestArgs) { name = "format" description = "Let's test formatting." From c3582aba5945d142f2561d3852be88254d63d401 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 22 Aug 2021 13:38:13 +0100 Subject: [PATCH 004/131] Extension functions are now extension functions. See what I did there? --- .../ext/common/extensions/EmojiExtension.kt | 1 + .../extra/mappings/MappingsExtension.kt | 1 + .../content/MessageContentGroupCommand.kt | 5 + .../kord/extensions/components/Components.kt | 1 + .../kord/extensions/extensions/Extension.kt | 202 +----------------- .../kord/extensions/extensions/_Commands.kt | 170 +++++++++++++++ .../kord/extensions/extensions/_Events.kt | 36 ++++ .../extensions/impl/HelpExtension.kt | 1 + .../extensions/impl/SentryExtension.kt | 2 + .../kord/extensions/test/bot/TestExtension.kt | 3 + .../kord/extensions/test/bot/TestExtension.kt | 1 + .../kord/extensions/test/bot/TestExtension.kt | 1 + 12 files changed, 229 insertions(+), 195 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt diff --git a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt index d095d6d5bc..a60ba0d400 100644 --- a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt +++ b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kordex.ext.common.extensions import com.kotlindiscord.kord.extensions.checks.inGuild import com.kotlindiscord.kord.extensions.checks.or import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.event import com.kotlindiscord.kordex.ext.common.builders.ExtCommonBuilder import com.kotlindiscord.kordex.ext.common.configuration.emoji.EmojiConfig import com.kotlindiscord.kordex.ext.common.emoji.NamedEmoji diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index fc6b8ec7ec..c4f9a63e5b 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandContext import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand import com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments.* import com.kotlindiscord.kord.extensions.modules.extra.mappings.builders.ExtMappingsBuilder import com.kotlindiscord.kord.extensions.modules.extra.mappings.enums.Channels diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt index 446cdc86c6..90cd87d2a0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt @@ -68,6 +68,7 @@ public open class MessageContentGroupCommand( * * @param body Builder lambda used for setting up the command object. */ + @ExtensionDSL public open suspend fun messageContentCommand( arguments: (() -> R)?, body: suspend MessageContentCommand.() -> Unit @@ -85,6 +86,7 @@ public open class MessageContentGroupCommand( * * @param body Builder lambda used for setting up the command object. */ + @ExtensionDSL public open suspend fun messageContentCommand( body: suspend MessageContentCommand.() -> Unit ): MessageContentCommand { @@ -101,6 +103,7 @@ public open class MessageContentGroupCommand( * * @param commandObj MessageCommand object to register. */ + @ExtensionDSL public open suspend fun messageContentCommand( commandObj: MessageContentCommand ): MessageContentCommand { @@ -126,6 +129,7 @@ public open class MessageContentGroupCommand( * * @param body Builder lambda used for setting up the command object. */ + @ExtensionDSL @Suppress("MemberNameEqualsClassName") // Really? public open suspend fun messageContentGroupCommand( arguments: (() -> R)?, @@ -147,6 +151,7 @@ public open class MessageContentGroupCommand( * * @param body Builder lambda used for setting up the command object. */ + @ExtensionDSL @Suppress("MemberNameEqualsClassName") // Really? public open suspend fun messageContentGroupCommand( body: suspend MessageContentGroupCommand.() -> Unit diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt index 01e8f6d3c4..87d34bd198 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.components.builders.* import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.event import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.entity.interaction.ComponentInteraction 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 52dea5c2a3..b07e5f1fa2 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 @@ -2,12 +2,11 @@ package com.kotlindiscord.kord.extensions.extensions -import com.kotlindiscord.kord.extensions.* +import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry -import com.kotlindiscord.kord.extensions.commands.content.MessageContentGroupCommand import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry @@ -39,10 +38,10 @@ public abstract class Extension : KoinComponent { public open val kord: Kord by inject() /** Message command registry. **/ - private val messageContentCommandsRegistry: MessageContentCommandRegistry by inject() + internal val messageContentCommandsRegistry: MessageContentCommandRegistry by inject() /** Slash command registry. **/ - private val slashCommandsRegistry: SlashCommandRegistry by inject() + internal val slashCommandsRegistry: SlashCommandRegistry by inject() /** * The name of this extension. @@ -139,164 +138,6 @@ public abstract class Extension : KoinComponent { this.state = state } - /** - * DSL function for easily registering a command. - * - * Use this in your setup function to register a command that may be executed on Discord. - * - * @param body Builder lambda used for setting up the command object. - */ - @ExtensionDSL - public open suspend fun messageContentCommand( - arguments: () -> T, - body: suspend MessageContentCommand.() -> Unit - ): MessageContentCommand { - val commandObj = MessageContentCommand(this, arguments) - body.invoke(commandObj) - - return messageContentCommand(commandObj) - } - - /** - * DSL function for easily registering a command, without arguments. - * - * Use this in your setup function to register a command that may be executed on Discord. - * - * @param body Builder lambda used for setting up the command object. - */ - @ExtensionDSL - public open suspend fun messageContentCommand( - body: suspend MessageContentCommand.() -> Unit - ): MessageContentCommand { - val commandObj = MessageContentCommand(this) - body.invoke(commandObj) - - return messageContentCommand(commandObj) - } - - /** - * Function for registering a custom command object. - * - * You can use this if you have a custom command subclass you need to register. - * - * @param commandObj MessageContentCommand object to register. - */ - public open suspend fun messageContentCommand( - commandObj: MessageContentCommand - ): MessageContentCommand { - try { - commandObj.validate() - messageContentCommandsRegistry.add(commandObj) - commands.add(commandObj) - } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register command - $e" } - } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register command - $e" } - } - - return commandObj - } - - /** - * DSL function for easily registering a slash command, with arguments. - * - * Use this in your setup function to register a slash command that may be executed on Discord. - * - * @param arguments Arguments builder (probably a reference to the class constructor). - * @param body Builder lambda used for setting up the slash command object. - */ - @ExtensionDSL - public open suspend fun slashCommand( - arguments: () -> T, - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(this, arguments) - body.invoke(commandObj) - - return slashCommand(commandObj) - } - - /** - * DSL function for easily registering a slash command, without arguments. - * - * Use this in your setup function to register a slash command that may be executed on Discord. - * - * @param body Builder lambda used for setting up the slash command object. - */ - @ExtensionDSL - public open suspend fun slashCommand( - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(this, null) - body.invoke(commandObj) - - return slashCommand(commandObj) - } - - /** - * Function for registering a custom slash command object. - * - * You can use this if you have a custom slash command subclass you need to register. - * - * @param commandObj SlashCommand object to register. - */ - public open suspend fun slashCommand( - commandObj: SlashCommand - ): SlashCommand { - try { - commandObj.validate() - slashCommands.add(commandObj) - slashCommandsRegistry.register(commandObj, commandObj.guild) - } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register slash command - $e" } - } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register slash command - $e" } - } - - return commandObj - } - - /** - * DSL function for easily registering a grouped command. - * - * Use this in your setup function to register a group of commands. - * - * The body of the grouped command will be executed if there is no - * matching subcommand. - * - * @param body Builder lambda used for setting up the command object. - */ - @ExtensionDSL - public open suspend fun messageContentGroupCommand( - arguments: () -> T, - body: suspend MessageContentGroupCommand.() -> Unit - ): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(this, arguments) - body.invoke(commandObj) - - return messageContentCommand(commandObj) as MessageContentGroupCommand - } - - /** - * DSL function for easily registering a grouped command, without its own arguments. - * - * Use this in your setup function to register a group of commands. - * - * The body of the grouped command will be executed if there is no - * matching subcommand. - * - * @param body Builder lambda used for setting up the command object. - */ - @ExtensionDSL - public open suspend fun messageContentGroupCommand( - body: suspend MessageContentGroupCommand.() -> Unit - ): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(this) - body.invoke(commandObj) - - return messageContentCommand(commandObj) as MessageContentGroupCommand - } - /** * If you need to, override this function and use it to clean up your extension when * it's unloaded. @@ -349,35 +190,6 @@ public abstract class Extension : KoinComponent { this.setState(ExtensionState.UNLOADED) } - /** - * DSL function for easily registering an event handler. - * - * Use this in your setup function to register an event handler that reacts to a given event. - * - * @param body Builder lambda used for setting up the event handler object. - */ - public suspend inline fun event( - noinline body: suspend EventHandler.() -> Unit - ): EventHandler { - val eventHandler = EventHandler(this) - val logger = KotlinLogging.logger {} - - body.invoke(eventHandler) - - try { - eventHandler.validate() - eventHandler.job = bot.addEventHandler(eventHandler) - - eventHandlers.add(eventHandler) - } catch (e: EventHandlerRegistrationException) { - logger.error(e) { "Failed to register event handler - $e" } - } catch (e: InvalidEventHandlerException) { - logger.error(e) { "Failed to register event handler - $e" } - } - - return eventHandler - } - /** * Define a check which must pass for the command to be executed. This check will be applied to all * slash commands in this extension. @@ -390,7 +202,7 @@ public abstract class Extension : KoinComponent { * * @param checks Checks to apply to all slash commands in this extension. */ - public open fun slashCheck(vararg checks: Check) { + public open fun slashCommandCheck(vararg checks: Check) { checks.forEach { slashCommandChecks.add(it) } } @@ -400,7 +212,7 @@ public abstract class Extension : KoinComponent { * @param check Check to apply to all slash commands in this extension. */ @ExtensionDSL - public open fun slashCheck(check: Check) { + public open fun slashCommandCheck(check: Check) { slashCommandChecks.add(check) } @@ -417,7 +229,7 @@ public abstract class Extension : KoinComponent { * @param checks Checks to apply to all commands in this extension. */ @ExtensionDSL - public open fun check(vararg checks: Check) { + public open fun messageContentCommandCheck(vararg checks: Check) { checks.forEach { commandChecks.add(it) } } @@ -427,7 +239,7 @@ public abstract class Extension : KoinComponent { * @param check Check to apply to all commands in this extension. */ @ExtensionDSL - public open fun check(check: Check) { + public open fun messageContentCommandCheck(check: Check) { commandChecks.add(check) } } 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 new file mode 100644 index 0000000000..b65b273a90 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Commands.kt @@ -0,0 +1,170 @@ +package com.kotlindiscord.kord.extensions.extensions + +import com.kotlindiscord.kord.extensions.CommandRegistrationException +import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand +import com.kotlindiscord.kord.extensions.commands.content.MessageContentGroupCommand +import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +/** + * DSL function for easily registering a command. + * + * Use this in your setup function to register a command that may be executed on Discord. + * + * @param body Builder lambda used for setting up the command object. + */ +@ExtensionDSL +public suspend fun Extension.messageContentCommand( + arguments: () -> T, + body: suspend MessageContentCommand.() -> Unit +): MessageContentCommand { + val commandObj = MessageContentCommand(this, arguments) + body.invoke(commandObj) + + return messageContentCommand(commandObj) +} + +/** + * DSL function for easily registering a command, without arguments. + * + * Use this in your setup function to register a command that may be executed on Discord. + * + * @param body Builder lambda used for setting up the command object. + */ +@ExtensionDSL +public suspend fun Extension.messageContentCommand( + body: suspend MessageContentCommand.() -> Unit +): MessageContentCommand { + val commandObj = MessageContentCommand(this) + body.invoke(commandObj) + + return messageContentCommand(commandObj) +} + +/** + * Function for registering a custom command object. + * + * You can use this if you have a custom command subclass you need to register. + * + * @param commandObj MessageContentCommand object to register. + */ +public fun Extension.messageContentCommand( + commandObj: MessageContentCommand +): MessageContentCommand { + try { + commandObj.validate() + messageContentCommandsRegistry.add(commandObj) + commands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register command - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register command - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering a slash command, with arguments. + * + * Use this in your setup function to register a slash command that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +@ExtensionDSL +public suspend fun Extension.slashCommand( + arguments: () -> T, + body: suspend SlashCommand.() -> Unit +): SlashCommand { + val commandObj = SlashCommand(this, arguments) + body.invoke(commandObj) + + return slashCommand(commandObj) +} + +/** + * DSL function for easily registering a slash command, without arguments. + * + * Use this in your setup function to register a slash command that may be executed on Discord. + * + * @param body Builder lambda used for setting up the slash command object. + */ +@ExtensionDSL +public suspend fun Extension.slashCommand( + body: suspend SlashCommand.() -> Unit +): SlashCommand { + val commandObj = SlashCommand(this, null) + body.invoke(commandObj) + + return slashCommand(commandObj) +} + +/** + * Function for registering a custom slash command object. + * + * You can use this if you have a custom slash command subclass you need to register. + * + * @param commandObj SlashCommand object to register. + */ +public fun Extension.slashCommand( + commandObj: SlashCommand +): SlashCommand { + try { + commandObj.validate() + slashCommands.add(commandObj) + slashCommandsRegistry.register(commandObj, commandObj.guild) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register slash command - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register slash command - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering a grouped command. + * + * Use this in your setup function to register a group of commands. + * + * The body of the grouped command will be executed if there is no + * matching subcommand. + * + * @param body Builder lambda used for setting up the command object. + */ +@ExtensionDSL +public suspend fun Extension.messageContentGroupCommand( + arguments: () -> T, + body: suspend MessageContentGroupCommand.() -> Unit +): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(this, arguments) + body.invoke(commandObj) + + return messageContentCommand(commandObj) as MessageContentGroupCommand +} + +/** + * DSL function for easily registering a grouped command, without its own arguments. + * + * Use this in your setup function to register a group of commands. + * + * The body of the grouped command will be executed if there is no + * matching subcommand. + * + * @param body Builder lambda used for setting up the command object. + */ +@ExtensionDSL +public suspend fun Extension.messageContentGroupCommand( + body: suspend MessageContentGroupCommand.() -> Unit +): MessageContentGroupCommand { + val commandObj = MessageContentGroupCommand(this) + body.invoke(commandObj) + + return messageContentCommand(commandObj) as MessageContentGroupCommand +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt new file mode 100644 index 0000000000..a1dc58c5fe --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt @@ -0,0 +1,36 @@ +package com.kotlindiscord.kord.extensions.extensions + +import com.kotlindiscord.kord.extensions.EventHandlerRegistrationException +import com.kotlindiscord.kord.extensions.InvalidEventHandlerException +import com.kotlindiscord.kord.extensions.events.EventHandler +import dev.kord.core.event.Event +import mu.KotlinLogging + +/** + * DSL function for easily registering an event handler. + * + * Use this in your setup function to register an event handler that reacts to a given event. + * + * @param body Builder lambda used for setting up the event handler object. + */ +public suspend inline fun Extension.event( + noinline body: suspend EventHandler.() -> Unit +): EventHandler { + val eventHandler = EventHandler(this) + val logger = KotlinLogging.logger {} + + body.invoke(eventHandler) + + try { + eventHandler.validate() + eventHandler.job = bot.addEventHandler(eventHandler) + + eventHandlers.add(eventHandler) + } catch (e: EventHandlerRegistrationException) { + logger.error(e) { "Failed to register event handler - $e" } + } catch (e: InvalidEventHandlerException) { + logger.error(e) { "Failed to register event handler - $e" } + } + + return eventHandler +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt index aef244fe68..7b7298f640 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.stringList import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.pagination.BasePaginator import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index ab85e82eec..ba4babdebc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -8,6 +8,8 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.string import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.slashCommand import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.sentryId import com.kotlindiscord.kord.extensions.utils.respond diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 0a6092ad0f..78cf88b4f6 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -8,6 +8,9 @@ import com.kotlindiscord.kord.extensions.commands.slash.AutoAckType import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported import com.kotlindiscord.kord.extensions.commands.slash.converters.impl.enumChoice import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.messageContentGroupCommand +import com.kotlindiscord.kord.extensions.extensions.slashCommand import com.kotlindiscord.kord.extensions.pagination.InteractionButtonPaginator import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index db458237c2..76f7f73c72 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -2,6 +2,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand import com.kotlindiscord.kord.extensions.modules.time.java.coalescedJ8Duration import com.kotlindiscord.kord.extensions.modules.time.java.toHuman import com.kotlindiscord.kord.extensions.utils.respond diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 7160b1763d..b4326bdca1 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -2,6 +2,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.messageContentCommand import com.kotlindiscord.kord.extensions.modules.time.time4j.coalescedT4jDuration import com.kotlindiscord.kord.extensions.modules.time.time4j.toHuman import com.kotlindiscord.kord.extensions.utils.respond From 110b123baad9640c15f52774da09ddf02cb0d02b Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 22 Aug 2021 15:34:54 +0100 Subject: [PATCH 005/131] Actually, let's call them chat commands --- .../extra/mappings/MappingsExtension.kt | 58 +++++++++---------- .../kord/extensions/Exceptions.kt | 14 ++--- .../kord/extensions/ExtensibleBot.kt | 18 +++--- .../builders/ExtensibleBotBuilder.kt | 10 ++-- .../ChatCommand.kt} | 18 +++--- .../ChatCommandContext.kt} | 8 +-- .../ChatCommandRegistry.kt} | 16 ++--- .../ChatGroupCommand.kt} | 48 +++++++-------- .../ChatSubCommand.kt} | 10 ++-- .../kord/extensions/extensions/Extension.kt | 24 ++++---- .../kord/extensions/extensions/_Commands.kt | 54 ++++++++--------- .../extensions/base/HelpProvider.kt | 34 +++++------ .../extensions/impl/HelpExtension.kt | 32 +++++----- .../extensions/impl/SentryExtension.kt | 4 +- .../kord/extensions/test/bot/TestExtension.kt | 32 +++++----- .../kord/extensions/test/bot/TestExtension.kt | 4 +- .../kord/extensions/test/bot/TestExtension.kt | 4 +- 17 files changed, 194 insertions(+), 194 deletions(-) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{content/MessageContentCommand.kt => chat/ChatCommand.kt} (96%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{content/MessageContentCommandContext.kt => chat/ChatCommandContext.kt} (94%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{content/MessageContentCommandRegistry.kt => chat/ChatCommandRegistry.kt} (93%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{content/MessageContentGroupCommand.kt => chat/ChatGroupCommand.kt} (81%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{content/MessageContentSubCommand.kt => chat/ChatSubCommand.kt} (77%) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index c4f9a63e5b..9b617529f1 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -4,10 +4,10 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings import com.kotlindiscord.kord.extensions.checks.and import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandContext +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments.* import com.kotlindiscord.kord.extensions.modules.extra.mappings.builders.ExtMappingsBuilder import com.kotlindiscord.kord.extensions.modules.extra.mappings.enums.Channels @@ -86,7 +86,7 @@ class MappingsExtension : Extension() { if (legacyYarnEnabled) { // Class - messageContentCommand(::LegacyYarnArguments) { + chatCommand(::LegacyYarnArguments) { name = "lyc" aliases = arrayOf("lyarnc", "legacy-yarnc", "legacyyarnc", "legacyarnc") @@ -104,7 +104,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand(::LegacyYarnArguments) { + chatCommand(::LegacyYarnArguments) { name = "lyf" aliases = arrayOf("lyarnf", "legacy-yarnf", "legacyyarnf", "legacyarnf") @@ -122,7 +122,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand(::LegacyYarnArguments) { + chatCommand(::LegacyYarnArguments) { name = "lym" aliases = arrayOf("lyarnm", "legacy-yarnm", "legacyyarnm", "legacyarnm") @@ -146,7 +146,7 @@ class MappingsExtension : Extension() { if (mcpEnabled) { // Class - messageContentCommand(::MCPArguments) { + chatCommand(::MCPArguments) { name = "mcpc" description = "Look up MCP mappings info for a class.\n\n" + @@ -162,7 +162,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand(::MCPArguments) { + chatCommand(::MCPArguments) { name = "mcpf" description = "Look up MCP mappings info for a field.\n\n" + @@ -178,7 +178,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand(::MCPArguments) { + chatCommand(::MCPArguments) { name = "mcpm" description = "Look up MCP mappings info for a method.\n\n" + @@ -200,7 +200,7 @@ class MappingsExtension : Extension() { if (mojangEnabled) { // Class - messageContentCommand(::MojangArguments) { + chatCommand(::MojangArguments) { name = "mmc" aliases = arrayOf("mojc", "mojmapc") @@ -221,7 +221,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand(::MojangArguments) { + chatCommand(::MojangArguments) { name = "mmf" aliases = arrayOf("mojf", "mojmapf") @@ -242,7 +242,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand(::MojangArguments) { + chatCommand(::MojangArguments) { name = "mmm" aliases = arrayOf("mojm", "mojmapm") @@ -269,7 +269,7 @@ class MappingsExtension : Extension() { if (plasmaEnabled) { // Class - messageContentCommand(::PlasmaArguments) { + chatCommand(::PlasmaArguments) { name = "pc" description = "Look up Plasma mappings info for a class.\n\n" + @@ -286,7 +286,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand(::PlasmaArguments) { + chatCommand(::PlasmaArguments) { name = "pf" description = "Look up Plasma mappings info for a field.\n\n" + @@ -303,7 +303,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand(::PlasmaArguments) { + chatCommand(::PlasmaArguments) { name = "pm" description = "Look up Plasma mappings info for a method.\n\n" + @@ -326,7 +326,7 @@ class MappingsExtension : Extension() { if (yarnEnabled) { // Class - messageContentCommand({ YarnArguments(patchworkEnabled) }) { + chatCommand({ YarnArguments(patchworkEnabled) }) { name = "yc" aliases = arrayOf("yarnc") @@ -352,7 +352,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand({ YarnArguments(patchworkEnabled) }) { + chatCommand({ YarnArguments(patchworkEnabled) }) { name = "yf" aliases = arrayOf("yarnf") @@ -378,7 +378,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand({ YarnArguments(patchworkEnabled) }) { + chatCommand({ YarnArguments(patchworkEnabled) }) { name = "ym" aliases = arrayOf("yarnm") @@ -410,7 +410,7 @@ class MappingsExtension : Extension() { if (yarrnEnabled) { // Class - messageContentCommand(::YarrnArguments) { + chatCommand(::YarrnArguments) { name = "yrc" description = "Look up Yarrn mappings info for a class.\n\n" + @@ -427,7 +427,7 @@ class MappingsExtension : Extension() { } // Field - messageContentCommand(::YarrnArguments) { + chatCommand(::YarrnArguments) { name = "yrf" description = "Look up Yarrn mappings info for a field.\n\n" + @@ -444,7 +444,7 @@ class MappingsExtension : Extension() { } // Method - messageContentCommand(::YarrnArguments) { + chatCommand(::YarrnArguments) { name = "yrm" description = "Look up Yarrn mappings info for a method.\n\n" + @@ -466,7 +466,7 @@ class MappingsExtension : Extension() { // region: Mappings info commands if (legacyYarnEnabled) { - messageContentCommand { + chatCommand { name = "lyarn" aliases = arrayOf("legacy-yarn", "legacyyarn", "legacyarn") @@ -533,7 +533,7 @@ class MappingsExtension : Extension() { } if (mcpEnabled) { - messageContentCommand { + chatCommand { name = "mcp" description = "Get information and a list of supported versions for MCP mappings." @@ -598,7 +598,7 @@ class MappingsExtension : Extension() { } if (mojangEnabled) { - messageContentCommand { + chatCommand { name = "mojang" aliases = arrayOf("mojmap") @@ -667,7 +667,7 @@ class MappingsExtension : Extension() { } if (plasmaEnabled) { - messageContentCommand { + chatCommand { name = "plasma" description = "Get information and a list of supported versions for Plasma mappings." @@ -733,7 +733,7 @@ class MappingsExtension : Extension() { } if (yarnEnabled) { - messageContentCommand { + chatCommand { name = "yarn" description = "Get information and a list of supported versions for Yarn mappings." @@ -827,7 +827,7 @@ class MappingsExtension : Extension() { } if (yarrnEnabled) { - messageContentCommand { + chatCommand { name = "yarrn" description = "Get information and a list of supported versions for Yarrn mappings." @@ -897,7 +897,7 @@ class MappingsExtension : Extension() { logger.info { "Mappings extension set up - namespaces: " + enabledNamespaces.joinToString(", ") } } - private suspend fun MessageContentCommandContext.queryClasses( + private suspend fun ChatCommandContext.queryClasses( namespace: Namespace, givenQuery: String, version: MappingsContainer?, @@ -1006,7 +1006,7 @@ class MappingsExtension : Extension() { paginator.send() } - private suspend fun MessageContentCommandContext.queryFields( + private suspend fun ChatCommandContext.queryFields( namespace: Namespace, givenQuery: String, version: MappingsContainer?, @@ -1115,7 +1115,7 @@ class MappingsExtension : Extension() { paginator.send() } - private suspend fun MessageContentCommandContext.queryMethods( + private suspend fun ChatCommandContext.queryMethods( namespace: Namespace, givenQuery: String, version: MappingsContainer?, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt index feeb43bf5a..a935dbe48b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.extensions.Extension import kotlin.reflect.KClass @@ -50,10 +50,10 @@ public class EventHandlerRegistrationException(public val reason: String) : Exte } /** - * Thrown when a [MessageContentCommand] could not be validated. + * Thrown when a [ChatCommand] could not be validated. * - * @param name The [MessageContentCommand] name - * @param reason Why this [MessageContentCommand] is considered invalid. + * @param name The [ChatCommand] name + * @param reason Why this [ChatCommand] is considered invalid. */ public class InvalidCommandException(public val name: String?, public val reason: String) : ExtensionsException() { override fun toString(): String { @@ -66,10 +66,10 @@ public class InvalidCommandException(public val name: String?, public val reason } /** - * Thrown when an attempt to register a [MessageContentCommand] fails. + * Thrown when an attempt to register a [ChatCommand] fails. * - * @param name The [MessageContentCommand] name - * @param reason Why this [MessageContentCommand] could not be registered. + * @param name The [ChatCommand] name + * @param reason Why this [ChatCommand] could not be registered. */ public class CommandRegistrationException(public val name: String?, public val reason: String) : ExtensionsException() { override fun toString(): String { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 920f19a9cc..50c73b2cc7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler @@ -169,7 +169,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva if (settings.messageCommandsBuilder.enabled) { on { - getKoin().get().handleEvent(this) + getKoin().get().handleEvent(this) } } @@ -308,7 +308,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } /** - * Directly register a [MessageContentCommand] to this bot. + * Directly register a [ChatCommand] to this bot. * * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and * call the [Extension.messageContentCommand] function in your [Extension.setup] function. @@ -331,12 +331,12 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva level = DeprecationLevel.ERROR ) @Throws(CommandRegistrationException::class) - public open fun addCommand(command: MessageContentCommand): Unit = getKoin() - .get() + public open fun addCommand(command: ChatCommand): Unit = getKoin() + .get() .add(command) /** - * Directly remove a registered [MessageContentCommand] from this bot. + * Directly remove a registered [ChatCommand] from this bot. * * This function is used when extensions are unloaded, in order to clear out their commands. * No exception is thrown if the command wasn't registered. @@ -354,8 +354,8 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva ), level = DeprecationLevel.ERROR ) - public open fun removeCommand(command: MessageContentCommand): Boolean = getKoin() - .get() + public open fun removeCommand(command: ChatCommand): Boolean = getKoin() + .get() .remove(command) /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 30385126e7..d29da60040 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.BotBuilderDSL import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations @@ -225,7 +225,7 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } - loadModule { single { messageCommandsBuilder.registryBuilder() } bind MessageContentCommandRegistry::class } + loadModule { single { messageCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } loadModule { single { slashCommandsBuilder.slashRegistryBuilder() } bind SlashCommandRegistry::class } loadModule { @@ -802,7 +802,7 @@ public open class ExtensibleBotBuilder { public var prefixCallback: suspend (MessageCreateEvent).(String) -> String = { defaultPrefix } /** @suppress Builder that shouldn't be set directly by the user. **/ - public var registryBuilder: () -> MessageContentCommandRegistry = { MessageContentCommandRegistry() } + public var registryBuilder: () -> ChatCommandRegistry = { ChatCommandRegistry() } /** * List of command checks. @@ -823,10 +823,10 @@ public open class ExtensibleBotBuilder { } /** - * Register the builder used to create the [MessageContentCommandRegistry]. You can change this if you need to + * Register the builder used to create the [ChatCommandRegistry]. You can change this if you need to * make use of a subclass. */ - public fun registry(builder: () -> MessageContentCommandRegistry) { + public fun registry(builder: () -> ChatCommandRegistry) { registryBuilder = builder } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt similarity index 96% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 2f9a2a5a90..f44a6442be 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -1,6 +1,6 @@ @file:Suppress("StringLiteralDuplication") -package com.kotlindiscord.kord.extensions.commands.content +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.InvalidCommandException @@ -47,7 +47,7 @@ private val logger = KotlinLogging.logger {} * @param arguments Arguments object builder for this command, if it has arguments. */ @ExtensionDSL -public open class MessageContentCommand( +public open class ChatCommand( extension: Extension, public open val arguments: (() -> T)? = null ) : Command(extension), KoinComponent { @@ -55,7 +55,7 @@ public open class MessageContentCommand( public val translationsProvider: TranslationsProvider by inject() /** Message command registry. **/ - public val messageCommandRegistry: MessageContentCommandRegistry by inject() + public val messageCommandRegistry: ChatCommandRegistry by inject() /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() @@ -66,7 +66,7 @@ public open class MessageContentCommand( /** * @suppress */ - public open lateinit var body: suspend MessageContentCommandContext.() -> Unit + public open lateinit var body: suspend ChatCommandContext.() -> Unit /** * A description of what this function and how it's intended to be used. @@ -225,7 +225,7 @@ public open class MessageContentCommand( * * @param action The body of your command, which will be executed when your command is invoked. */ - public open fun action(action: suspend MessageContentCommandContext.() -> Unit) { + public open fun action(action: suspend ChatCommandContext.() -> Unit) { this.body = action } @@ -320,7 +320,7 @@ public open class MessageContentCommand( } // local extension checks - for (check in extension.commandChecks) { + for (check in extension.chatCommandChecks) { val context = CheckContext(event, locale) check(context) @@ -391,7 +391,7 @@ public open class MessageContentCommand( return } - val context = MessageContentCommandContext(this, event, commandName, parser, argString) + val context = ChatCommandContext(this, event, commandName, parser, argString) context.populate() @@ -464,8 +464,8 @@ public open class MessageContentCommand( val channel = event.message.getChannelOrNull() val translatedName = when (this) { - is MessageContentSubCommand -> this.getFullTranslatedName(context.getLocale()) - is MessageContentGroupCommand -> this.getFullTranslatedName(context.getLocale()) + is ChatSubCommand -> this.getFullTranslatedName(context.getLocale()) + is ChatGroupCommand -> this.getFullTranslatedName(context.getLocale()) else -> this.getTranslatedName(context.getLocale()) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt similarity index 94% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index d85d6ec30f..7e05bce40c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -1,6 +1,6 @@ @file:OptIn(KordPreview::class) -package com.kotlindiscord.kord.extensions.commands.content +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -24,12 +24,12 @@ import dev.kord.rest.builder.message.modify.MessageModifyBuilder /** * Command context object representing the context given to message commands. * - * @property messageCommand Message command object, typed as [MessageContentCommand] rather than [Command] + * @property messageCommand Message command object, typed as [ChatCommand] rather than [Command] * @property argString String containing the command's unparsed arguments, raw, fresh from Discord itself. */ @ExtensionDSL -public open class MessageContentCommandContext( - public val messageCommand: MessageContentCommand, +public open class ChatCommandContext( + public val messageCommand: ChatCommand, eventObj: MessageCreateEvent, commandName: String, parser: StringParser, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt similarity index 93% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt index 2f06a13891..34114c2765 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.content +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.ExtensibleBot @@ -21,7 +21,7 @@ import java.util.concurrent.Executors * A class for the registration and dispatching of message-based commands. */ @OptIn(KordPreview::class) -public open class MessageContentCommandRegistry : KoinComponent { +public open class ChatCommandRegistry : KoinComponent { /** Current instance of the bot. **/ public val bot: ExtensibleBot by inject() @@ -31,7 +31,7 @@ public open class MessageContentCommandRegistry : KoinComponent { /** * A list of all registered commands. */ - public open val commands: MutableList> = mutableListOf() + public open val commands: MutableList> = mutableListOf() /** @suppress **/ public val botSettings: ExtensibleBotBuilder by inject() @@ -44,7 +44,7 @@ public open class MessageContentCommandRegistry : KoinComponent { } /** - * Directly register a [MessageContentCommand] to this command registry. + * Directly register a [ChatCommand] to this command registry. * * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and * call the [Extension.messageContentCommand] function in your [Extension.setup] function. @@ -56,7 +56,7 @@ public open class MessageContentCommandRegistry : KoinComponent { * @throws CommandRegistrationException Thrown if the command could not be registered. */ @Throws(CommandRegistrationException::class) - public open fun add(command: MessageContentCommand) { + public open fun add(command: ChatCommand) { val existingCommand = commands.any { it.name == command.name } val existingAlias: String? = commands.flatMap { it.aliases.toList() @@ -88,14 +88,14 @@ public open class MessageContentCommandRegistry : KoinComponent { } /** - * Directly remove a registered [MessageContentCommand] from this command registry. + * Directly remove a registered [ChatCommand] from this command registry. * * This function is used when extensions are unloaded, in order to clear out their commands. * No exception is thrown if the command wasn't registered. * * @param command The command to be removed. */ - public open fun remove(command: MessageContentCommand): Boolean = commands.remove(command) + public open fun remove(command: ChatCommand): Boolean = commands.remove(command) /** * Given a [MessageCreateEvent], return the prefix that should be used for a command invocation. @@ -243,7 +243,7 @@ public open class MessageContentCommandRegistry : KoinComponent { * * If a command supports locale fallback, this will also attempt to resolve names via the bot's default locale. */ - public open suspend fun getCommand(name: String, event: MessageCreateEvent): MessageContentCommand? { + public open suspend fun getCommand(name: String, event: MessageCreateEvent): ChatCommand? { val defaultLocale = botSettings.i18nBuilder.defaultLocale val locale = event.getLocale() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt similarity index 81% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index 90cd87d2a0..e5990ade1d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.content +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException @@ -22,26 +22,26 @@ private val logger = KotlinLogging.logger {} * `group` function to register your command group, by overriding the `Extension` setup function. * * @param extension The extension that registered this grouped command. - * @param parent The [MessageContentGroupCommand] this group exists under, if any. + * @param parent The [ChatGroupCommand] this group exists under, if any. */ @Suppress("LateinitVarOverridesLateinitVar") // This is intentional @ExtensionDSL -public open class MessageContentGroupCommand( +public open class ChatGroupCommand( extension: Extension, arguments: (() -> T)? = null, - public open val parent: MessageContentGroupCommand? = null -) : MessageContentCommand(extension, arguments) { + public open val parent: ChatGroupCommand? = null +) : ChatCommand(extension, arguments) { /** @suppress **/ public val botSettings: ExtensibleBotBuilder by inject() /** @suppress **/ - public open val commands: MutableList> = mutableListOf() + public open val commands: MutableList> = mutableListOf() override lateinit var name: String /** @suppress **/ - override var body: suspend MessageContentCommandContext.() -> Unit = { + override var body: suspend ChatCommandContext.() -> Unit = { sendHelp() } @@ -71,9 +71,9 @@ public open class MessageContentGroupCommand( @ExtensionDSL public open suspend fun messageContentCommand( arguments: (() -> R)?, - body: suspend MessageContentCommand.() -> Unit - ): MessageContentCommand { - val commandObj = MessageContentSubCommand(extension, arguments, this) + body: suspend ChatCommand.() -> Unit + ): ChatCommand { + val commandObj = ChatSubCommand(extension, arguments, this) body.invoke(commandObj) return messageContentCommand(commandObj) @@ -88,9 +88,9 @@ public open class MessageContentGroupCommand( */ @ExtensionDSL public open suspend fun messageContentCommand( - body: suspend MessageContentCommand.() -> Unit - ): MessageContentCommand { - val commandObj = MessageContentSubCommand(extension, parent = this) + body: suspend ChatCommand.() -> Unit + ): ChatCommand { + val commandObj = ChatSubCommand(extension, parent = this) body.invoke(commandObj) return messageContentCommand(commandObj) @@ -105,8 +105,8 @@ public open class MessageContentGroupCommand( */ @ExtensionDSL public open suspend fun messageContentCommand( - commandObj: MessageContentCommand - ): MessageContentCommand { + commandObj: ChatCommand + ): ChatCommand { try { commandObj.validate() commands.add(commandObj) @@ -133,12 +133,12 @@ public open class MessageContentGroupCommand( @Suppress("MemberNameEqualsClassName") // Really? public open suspend fun messageContentGroupCommand( arguments: (() -> R)?, - body: suspend MessageContentGroupCommand.() -> Unit - ): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(extension, arguments, this) + body: suspend ChatGroupCommand.() -> Unit + ): ChatGroupCommand { + val commandObj = ChatGroupCommand(extension, arguments, this) body.invoke(commandObj) - return messageContentCommand(commandObj) as MessageContentGroupCommand + return messageContentCommand(commandObj) as ChatGroupCommand } /** @@ -154,19 +154,19 @@ public open class MessageContentGroupCommand( @ExtensionDSL @Suppress("MemberNameEqualsClassName") // Really? public open suspend fun messageContentGroupCommand( - body: suspend MessageContentGroupCommand.() -> Unit - ): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(extension, parent = this) + body: suspend ChatGroupCommand.() -> Unit + ): ChatGroupCommand { + val commandObj = ChatGroupCommand(extension, parent = this) body.invoke(commandObj) - return messageContentCommand(commandObj) as MessageContentGroupCommand + return messageContentCommand(commandObj) as ChatGroupCommand } /** @suppress **/ public open suspend fun getCommand( name: String?, event: MessageCreateEvent - ): MessageContentCommand? { + ): ChatCommand? { name ?: return null val defaultLocale = botSettings.i18nBuilder.defaultLocale diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt similarity index 77% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt index df357cc092..ed918b1417 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/content/MessageContentSubCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.content +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.parser.Arguments @@ -11,14 +11,14 @@ import java.util.* * This is used for group commands, so that subcommands are aware of their parent. * * @param extension The [Extension] that registered this command. - * @param parent The [MessageContentGroupCommand] this command exists under. + * @param parent The [ChatGroupCommand] this command exists under. */ @ExtensionDSL -public open class MessageContentSubCommand( +public open class ChatSubCommand( extension: Extension, arguments: (() -> T)? = null, - public open val parent: MessageContentGroupCommand -) : MessageContentCommand(extension, arguments) { + public open val parent: ChatGroupCommand +) : ChatCommand(extension, arguments) { /** Get the full command name, translated, with parent commands taken into account. **/ public open suspend fun getFullTranslatedName(locale: Locale): String = parent.getFullTranslatedName(locale) + " " + this.getTranslatedName(locale) 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 b07e5f1fa2..b9a6addb6d 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 @@ -5,8 +5,8 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry @@ -38,7 +38,7 @@ public abstract class Extension : KoinComponent { public open val kord: Kord by inject() /** Message command registry. **/ - internal val messageContentCommandsRegistry: MessageContentCommandRegistry by inject() + internal val chatCommandRegistry: ChatCommandRegistry by inject() /** Slash command registry. **/ internal val slashCommandsRegistry: SlashCommandRegistry by inject() @@ -72,7 +72,7 @@ public abstract class Extension : KoinComponent { * * When an extension is unloaded, all the commands are removed from the bot. */ - public open val commands: MutableList> = mutableListOf() + public open val chatCommands: MutableList> = mutableListOf() /** * List of registered slash commands. @@ -87,7 +87,7 @@ public abstract class Extension : KoinComponent { * * These checks will be checked against all commands in this extension. */ - public open val commandChecks: MutableList> = + public open val chatCommandChecks: MutableList> = mutableListOf() /** @@ -176,12 +176,12 @@ public abstract class Extension : KoinComponent { bot.removeEventHandler(handler) } - for (command in commands) { - messageContentCommandsRegistry.remove(command) + for (command in chatCommands) { + chatCommandRegistry.remove(command) } eventHandlers.clear() - commands.clear() + chatCommands.clear() if (error != null) { throw error @@ -229,8 +229,8 @@ public abstract class Extension : KoinComponent { * @param checks Checks to apply to all commands in this extension. */ @ExtensionDSL - public open fun messageContentCommandCheck(vararg checks: Check) { - checks.forEach { commandChecks.add(it) } + public open fun chatCommandCheck(vararg checks: Check) { + checks.forEach { chatCommandChecks.add(it) } } /** @@ -239,7 +239,7 @@ public abstract class Extension : KoinComponent { * @param check Check to apply to all commands in this extension. */ @ExtensionDSL - public open fun messageContentCommandCheck(check: Check) { - commandChecks.add(check) + public open fun chatCommandCheck(check: Check) { + chatCommandChecks.add(check) } } 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 b65b273a90..651ec8d8d9 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 @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand -import com.kotlindiscord.kord.extensions.commands.content.MessageContentGroupCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import mu.KotlinLogging @@ -19,14 +19,14 @@ private val logger = KotlinLogging.logger {} * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL -public suspend fun Extension.messageContentCommand( +public suspend fun Extension.chatCommand( arguments: () -> T, - body: suspend MessageContentCommand.() -> Unit -): MessageContentCommand { - val commandObj = MessageContentCommand(this, arguments) + body: suspend ChatCommand.() -> Unit +): ChatCommand { + val commandObj = ChatCommand(this, arguments) body.invoke(commandObj) - return messageContentCommand(commandObj) + return chatCommand(commandObj) } /** @@ -37,13 +37,13 @@ public suspend fun Extension.messageContentCommand( * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL -public suspend fun Extension.messageContentCommand( - body: suspend MessageContentCommand.() -> Unit -): MessageContentCommand { - val commandObj = MessageContentCommand(this) +public suspend fun Extension.chatCommand( + body: suspend ChatCommand.() -> Unit +): ChatCommand { + val commandObj = ChatCommand(this) body.invoke(commandObj) - return messageContentCommand(commandObj) + return chatCommand(commandObj) } /** @@ -53,13 +53,13 @@ public suspend fun Extension.messageContentCommand( * * @param commandObj MessageContentCommand object to register. */ -public fun Extension.messageContentCommand( - commandObj: MessageContentCommand -): MessageContentCommand { +public fun Extension.chatCommand( + commandObj: ChatCommand +): ChatCommand { try { commandObj.validate() - messageContentCommandsRegistry.add(commandObj) - commands.add(commandObj) + chatCommandRegistry.add(commandObj) + chatCommands.add(commandObj) } catch (e: CommandRegistrationException) { logger.error(e) { "Failed to register command - $e" } } catch (e: InvalidCommandException) { @@ -139,14 +139,14 @@ public fun Extension.slashCommand( * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL -public suspend fun Extension.messageContentGroupCommand( +public suspend fun Extension.chatGroupCommand( arguments: () -> T, - body: suspend MessageContentGroupCommand.() -> Unit -): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(this, arguments) + body: suspend ChatGroupCommand.() -> Unit +): ChatGroupCommand { + val commandObj = ChatGroupCommand(this, arguments) body.invoke(commandObj) - return messageContentCommand(commandObj) as MessageContentGroupCommand + return chatCommand(commandObj) as ChatGroupCommand } /** @@ -160,11 +160,11 @@ public suspend fun Extension.messageContentGroupCommand( * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL -public suspend fun Extension.messageContentGroupCommand( - body: suspend MessageContentGroupCommand.() -> Unit -): MessageContentGroupCommand { - val commandObj = MessageContentGroupCommand(this) +public suspend fun Extension.chatGroupCommand( + body: suspend ChatGroupCommand.() -> Unit +): ChatGroupCommand { + val commandObj = ChatGroupCommand(this) body.invoke(commandObj) - return messageContentCommand(commandObj) as MessageContentGroupCommand + return chatCommand(commandObj) as ChatGroupCommand } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt index f327b38504..a91fbb7c39 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt @@ -1,8 +1,8 @@ package com.kotlindiscord.kord.extensions.extensions.base -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommand -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandContext -import com.kotlindiscord.kord.extensions.commands.content.MessageContentCommandRegistry +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.pagination.BasePaginator import com.kotlindiscord.kord.extensions.utils.getKoin @@ -34,7 +34,7 @@ public interface HelpProvider { public suspend fun formatCommandHelp( prefix: String, event: MessageCreateEvent, - command: MessageContentCommand, + command: ChatCommand, longDescription: Boolean = false ): Triple @@ -51,11 +51,11 @@ public interface HelpProvider { * description, and the command's argument list. */ public suspend fun formatCommandHelp( - context: MessageContentCommandContext<*>, - command: MessageContentCommand, + context: ChatCommandContext<*>, + command: ChatCommand, longDescription: Boolean = false ): Triple { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return formatCommandHelp(prefix, context.event, command, longDescription) } @@ -63,12 +63,12 @@ public interface HelpProvider { /** * Gather all available commands (with passing checks) from the bot, and return them. */ - public suspend fun gatherCommands(event: MessageCreateEvent): List> + public suspend fun gatherCommands(event: MessageCreateEvent): List> /** * Return the [MessageCommand] specified in the arguments, or `null` if it can't be found (or the checks fail). */ - public suspend fun getCommand(event: MessageCreateEvent, args: List): MessageContentCommand? + public suspend fun getCommand(event: MessageCreateEvent, args: List): ChatCommand? /** * Given an event, prefix and argument list, attempt to find the command represented by the arguments and return @@ -100,10 +100,10 @@ public interface HelpProvider { * @return Paginator containing the command's help, or an error message. */ public suspend fun getCommandHelpPaginator( - context: MessageContentCommandContext<*>, + context: ChatCommandContext<*>, args: List ): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return getCommandHelpPaginator(context.event, prefix, args) } @@ -126,7 +126,7 @@ public interface HelpProvider { public suspend fun getCommandHelpPaginator( event: MessageCreateEvent, prefix: String, - command: MessageContentCommand? + command: ChatCommand? ): BasePaginator /** @@ -144,10 +144,10 @@ public interface HelpProvider { * @return Paginator containing the command's help, or an error message. */ public suspend fun getCommandHelpPaginator( - context: MessageContentCommandContext<*>, - command: MessageContentCommand? + context: ChatCommandContext<*>, + command: ChatCommand? ): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + val prefix = getKoin().get().getPrefix(context.event) return getCommandHelpPaginator(context.event, prefix, command) } @@ -183,8 +183,8 @@ public interface HelpProvider { * * @return BasePaginator containing help information for all loaded commands with passing checks. */ - public suspend fun getMainHelpPaginator(context: MessageContentCommandContext<*>): BasePaginator { - val prefix = getKoin().get().getPrefix(context.event) + public suspend fun getMainHelpPaginator(context: ChatCommandContext<*>): BasePaginator { + val prefix = getKoin().get().getPrefix(context.event) return getMainHelpPaginator(context.event, prefix) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt index 7b7298f640..681c4719a6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt @@ -3,12 +3,12 @@ package com.kotlindiscord.kord.extensions.extensions.impl import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.content.* +import com.kotlindiscord.kord.extensions.commands.chat.* import com.kotlindiscord.kord.extensions.commands.converters.impl.stringList import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.pagination.BasePaginator import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator @@ -44,7 +44,7 @@ public class HelpExtension : HelpProvider, Extension() { public val translationsProvider: TranslationsProvider by inject() /** Message command registry. **/ - public val messageCommandsRegistry: MessageContentCommandRegistry by inject() + public val messageCommandsRegistry: ChatCommandRegistry by inject() /** Bot settings. **/ public val botSettings: ExtensibleBotBuilder by inject() @@ -54,7 +54,7 @@ public class HelpExtension : HelpProvider, Extension() { botSettings.extensionsBuilder.helpExtensionBuilder override suspend fun setup() { - messageContentCommand(::HelpArguments) { + chatCommand(::HelpArguments) { name = "extensions.help.commandName" aliasKey = "extensions.help.commandAliases" description = "extensions.help.commandDescription" @@ -176,7 +176,7 @@ public class HelpExtension : HelpProvider, Extension() { ): BasePaginator = getCommandHelpPaginator(event, prefix, getCommand(event, args)) override suspend fun getCommandHelpPaginator( - context: MessageContentCommandContext<*>, + context: ChatCommandContext<*>, args: List ): BasePaginator = getCommandHelpPaginator(context, getCommand(context.event, args)) @@ -184,7 +184,7 @@ public class HelpExtension : HelpProvider, Extension() { override suspend fun getCommandHelpPaginator( event: MessageCreateEvent, prefix: String, - command: MessageContentCommand? + command: ChatCommand? ): BasePaginator { val pages = Pages(COMMANDS_GROUP) val locale = event.getLocale() @@ -210,9 +210,9 @@ public class HelpExtension : HelpProvider, Extension() { } else { val (openingLine, desc, arguments) = formatCommandHelp(prefix, event, command, longDescription = true) - val commandName = if (command is MessageContentSubCommand) { + val commandName = if (command is ChatSubCommand) { command.getFullTranslatedName(locale) - } else if (command is MessageContentGroupCommand) { + } else if (command is ChatGroupCommand) { command.getFullTranslatedName(locale) } else { command.getTranslatedName(locale) @@ -255,7 +255,7 @@ public class HelpExtension : HelpProvider, Extension() { } } - override suspend fun gatherCommands(event: MessageCreateEvent): List> = + override suspend fun gatherCommands(event: MessageCreateEvent): List> = messageCommandsRegistry.commands .filter { !it.hidden && it.enabled && it.runChecks(event, false) } .sortedBy { it.name } @@ -263,15 +263,15 @@ public class HelpExtension : HelpProvider, Extension() { override suspend fun formatCommandHelp( prefix: String, event: MessageCreateEvent, - command: MessageContentCommand, + command: ChatCommand, longDescription: Boolean ): Triple { val locale = event.getLocale() val defaultLocale = botSettings.i18nBuilder.defaultLocale - val commandName = if (command is MessageContentSubCommand) { + val commandName = if (command is ChatSubCommand) { command.getFullTranslatedName(locale) - } else if (command is MessageContentGroupCommand) { + } else if (command is ChatGroupCommand) { command.getFullTranslatedName(locale) } else { command.getTranslatedName(locale) @@ -311,7 +311,7 @@ public class HelpExtension : HelpProvider, Extension() { } } - if (command is MessageContentGroupCommand) { + if (command is ChatGroupCommand) { val subCommands = command.commands.filter { it.runChecks(event, false) } if (subCommands.isNotEmpty()) { @@ -386,7 +386,7 @@ public class HelpExtension : HelpProvider, Extension() { override suspend fun getCommand( event: MessageCreateEvent, args: List - ): MessageContentCommand? { + ): ChatCommand? { val firstArg = args.first() var command = messageCommandsRegistry.getCommand(firstArg, event) @@ -395,8 +395,8 @@ public class HelpExtension : HelpProvider, Extension() { } args.drop(1).forEach { - if (command is MessageContentGroupCommand) { - val gc = command as MessageContentGroupCommand + if (command is ChatGroupCommand) { + val gc = command as ChatGroupCommand command = if (gc.runChecks(event, false)) { gc.getCommand(it, event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index ba4babdebc..91fb2f6b54 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.string import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.extensions.slashCommand import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.sentryId @@ -69,7 +69,7 @@ public class SentryExtension : Extension() { } } - messageContentCommand(::FeedbackMessageArgs) { + chatCommand(::FeedbackMessageArgs) { name = "extensions.sentry.commandName" description = "extensions.sentry.commandDescription.long" diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 78cf88b4f6..cdc2c7e42c 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -8,8 +8,8 @@ import com.kotlindiscord.kord.extensions.commands.slash.AutoAckType import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported import com.kotlindiscord.kord.extensions.commands.slash.converters.impl.enumChoice import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand -import com.kotlindiscord.kord.extensions.extensions.messageContentGroupCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand +import com.kotlindiscord.kord.extensions.extensions.chatGroupCommand import com.kotlindiscord.kord.extensions.extensions.slashCommand import com.kotlindiscord.kord.extensions.pagination.InteractionButtonPaginator import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator @@ -73,7 +73,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - messageContentCommand(::ColorArgs) { + chatCommand(::ColorArgs) { name = "color" aliases = arrayOf("colour") description = "Get an embed with a set color" @@ -89,7 +89,7 @@ class TestExtension : Extension() { } } - messageContentCommand(::MessageArgs) { + chatCommand(::MessageArgs) { name = "msg" description = "Message argument test" @@ -100,7 +100,7 @@ class TestExtension : Extension() { } } - messageContentCommand(::CoalescedArgs) { + chatCommand(::CoalescedArgs) { name = "coalesce" description = "Coalesce me, baby" @@ -118,7 +118,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "dropdown" description = "Dropdown test!" @@ -437,7 +437,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "translation-test" description = "Let's test translations." @@ -447,7 +447,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "requires-perms" description = "A command that requires some permissions" @@ -458,7 +458,7 @@ class TestExtension : Extension() { } } - messageContentCommand(::TestArgs) { + chatCommand(::TestArgs) { name = "test" description = "Test command, please ignore\n\n" + @@ -499,7 +499,7 @@ class TestExtension : Extension() { } } - messageContentCommand(::TestArgs) { + chatCommand(::TestArgs) { name = "test-help" description = "Sends help for this command.\n\n" + @@ -510,7 +510,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "page" description = "Paginator test" @@ -571,7 +571,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "page2" description = "Paginator test 2" @@ -614,11 +614,11 @@ class TestExtension : Extension() { } } - messageContentGroupCommand { + chatGroupCommand { name = "group" description = "Command group" - messageContentCommand { + chatCommand { name = "one" description = "one" @@ -627,7 +627,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "two" description = "two" @@ -636,7 +636,7 @@ class TestExtension : Extension() { } } - messageContentCommand { + chatCommand { name = "three" description = "three" diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 76f7f73c72..8d3beeb8f2 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.time.java.coalescedJ8Duration import com.kotlindiscord.kord.extensions.modules.time.java.toHuman import com.kotlindiscord.kord.extensions.utils.respond @@ -23,7 +23,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - messageContentCommand(::TestArgs) { + chatCommand(::TestArgs) { name = "format" description = "Let's test formatting." diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index b4326bdca1..a9df7d36ea 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.messageContentCommand +import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.time.time4j.coalescedT4jDuration import com.kotlindiscord.kord.extensions.modules.time.time4j.toHuman import com.kotlindiscord.kord.extensions.utils.respond @@ -23,7 +23,7 @@ class TestExtension : Extension() { } override suspend fun setup() { - messageContentCommand(::TestArgs) { + chatCommand(::TestArgs) { name = "format" description = "Let's test formatting." From c0c4674f1bc679f45427035382017a88d4d1f0d3 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 22 Aug 2021 17:18:21 +0100 Subject: [PATCH 006/131] Chase snapshot, fix slash commands --- .../kord/extensions/checks/CheckUtils.kt | 14 +++---- .../commands/chat/ChatGroupCommand.kt | 18 ++++---- .../extensions/commands/slash/SlashCommand.kt | 10 ++--- .../commands/slash/SlashCommandRegistry.kt | 29 ++++++------- .../kord/extensions/utils/_Permissions.kt | 6 ++- .../kord/extensions/utils/_Translation.kt | 41 ++----------------- .../translations/kordex/strings.properties | 5 ++- .../kordex/strings_en_GB.properties | 6 ++- 8 files changed, 46 insertions(+), 83 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 6f422a6d28..4a40b17057 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -14,7 +14,7 @@ import dev.kord.core.event.Event import dev.kord.core.event.channel.* import dev.kord.core.event.channel.thread.* import dev.kord.core.event.guild.* -import dev.kord.core.event.interaction.ApplicationCreateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent import dev.kord.core.event.message.* import dev.kord.core.event.role.RoleCreateEvent import dev.kord.core.event.role.RoleDeleteEvent @@ -40,7 +40,7 @@ public suspend fun channelFor(event: Event): ChannelBehavior? { is ChannelDeleteEvent -> event.channel is ChannelPinsUpdateEvent -> event.channel is ChannelUpdateEvent -> event.channel - is ApplicationCreateEvent -> event.interaction.channel + is InteractionCreateEvent -> event.interaction.channel is InviteCreateEvent -> event.channel is InviteDeleteEvent -> event.channel is MessageBulkDeleteEvent -> event.channel @@ -104,7 +104,7 @@ public suspend fun channelIdFor(event: Event): Long? { is ChannelDeleteEvent -> event.channel.id.value is ChannelPinsUpdateEvent -> event.channel.id.value is ChannelUpdateEvent -> event.channel.id.value - is ApplicationCreateEvent -> event.interaction.channel.id.value + is InteractionCreateEvent -> event.interaction.channel.id.value is InviteCreateEvent -> event.channel.id.value is InviteDeleteEvent -> event.channel.id.value is MessageBulkDeleteEvent -> event.channelId.value @@ -146,7 +146,7 @@ public suspend fun channelSnowflakeFor(event: Event): Snowflake? { is ChannelDeleteEvent -> event.channel.id is ChannelPinsUpdateEvent -> event.channel.id is ChannelUpdateEvent -> event.channel.id - is ApplicationCreateEvent -> event.interaction.channel.id + is InteractionCreateEvent -> event.interaction.channel.id is InviteCreateEvent -> event.channel.id is InviteDeleteEvent -> event.channel.id is MessageBulkDeleteEvent -> event.channelId @@ -196,7 +196,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? { is GuildUpdateEvent -> event.guild is IntegrationsUpdateEvent -> event.guild - is ApplicationCreateEvent -> { + is InteractionCreateEvent -> { val guildId = event.interaction.data.guildId.value if (guildId == null) { @@ -258,7 +258,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? { */ public suspend fun memberFor(event: Event): MemberBehavior? { return when { - event is ApplicationCreateEvent -> (event.interaction as? GuildApplicationCommandInteraction)?.member + event is InteractionCreateEvent -> (event.interaction as? GuildApplicationCommandInteraction)?.member event is MemberJoinEvent -> event.member event is MemberUpdateEvent -> event.member @@ -399,7 +399,7 @@ public suspend fun userFor(event: Event): UserBehavior? { is DMChannelCreateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelDeleteEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelUpdateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } - is ApplicationCreateEvent -> event.interaction.user + is InteractionCreateEvent -> event.interaction.user is MemberJoinEvent -> event.member is MemberLeaveEvent -> event.user is MemberUpdateEvent -> event.member diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index e5990ade1d..43da579b0a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -69,14 +69,14 @@ public open class ChatGroupCommand( * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun messageContentCommand( + public open suspend fun chatCommand( arguments: (() -> R)?, body: suspend ChatCommand.() -> Unit ): ChatCommand { val commandObj = ChatSubCommand(extension, arguments, this) body.invoke(commandObj) - return messageContentCommand(commandObj) + return chatCommand(commandObj) } /** @@ -87,13 +87,13 @@ public open class ChatGroupCommand( * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL - public open suspend fun messageContentCommand( + public open suspend fun chatCommand( body: suspend ChatCommand.() -> Unit ): ChatCommand { val commandObj = ChatSubCommand(extension, parent = this) body.invoke(commandObj) - return messageContentCommand(commandObj) + return chatCommand(commandObj) } /** @@ -104,7 +104,7 @@ public open class ChatGroupCommand( * @param commandObj MessageCommand object to register. */ @ExtensionDSL - public open suspend fun messageContentCommand( + public open suspend fun chatCommand( commandObj: ChatCommand ): ChatCommand { try { @@ -131,14 +131,14 @@ public open class ChatGroupCommand( */ @ExtensionDSL @Suppress("MemberNameEqualsClassName") // Really? - public open suspend fun messageContentGroupCommand( + public open suspend fun chatGroupCommand( arguments: (() -> R)?, body: suspend ChatGroupCommand.() -> Unit ): ChatGroupCommand { val commandObj = ChatGroupCommand(extension, arguments, this) body.invoke(commandObj) - return messageContentCommand(commandObj) as ChatGroupCommand + return chatCommand(commandObj) as ChatGroupCommand } /** @@ -153,13 +153,13 @@ public open class ChatGroupCommand( */ @ExtensionDSL @Suppress("MemberNameEqualsClassName") // Really? - public open suspend fun messageContentGroupCommand( + public open suspend fun chatGroupCommand( body: suspend ChatGroupCommand.() -> Unit ): ChatGroupCommand { val commandObj = ChatGroupCommand(extension, parent = this) body.invoke(commandObj) - return messageContentCommand(commandObj) as ChatGroupCommand + return chatCommand(commandObj) as ChatGroupCommand } /** @suppress **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt index d1d8f7b82e..f5bea2669d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt @@ -34,7 +34,6 @@ import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.core.entity.interaction.CommandInteraction import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent @@ -577,10 +576,7 @@ public open class SlashCommand( * @param event The interaction creation event. */ public open suspend fun call(event: ChatInputCommandInteractionCreateEvent) { - if (event.interaction !is CommandInteraction) return - - val interaction = event.interaction as CommandInteraction - val eventCommand = interaction.command + val eventCommand = event.interaction.command // We lie to the compiler thrice below to work around an issue with generics. val commandObj: SlashCommand = if (eventCommand is SubCommand) { @@ -604,8 +600,8 @@ public open class SlashCommand( } val resp = when (commandObj.autoAck) { - AutoAckType.EPHEMERAL -> interaction.acknowledgeEphemeral() - AutoAckType.PUBLIC -> interaction.acknowledgePublic() + AutoAckType.EPHEMERAL -> event.interaction.acknowledgeEphemeral() + AutoAckType.PUBLIC -> event.interaction.acknowledgePublic() AutoAckType.NONE -> null } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt index 289ea87690..831d44b122 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt @@ -11,10 +11,10 @@ import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.Kord -import dev.kord.core.SlashCommands -import dev.kord.core.entity.interaction.CommandInteraction import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.builder.interaction.* +import dev.kord.rest.builder.interaction.ChatInputCreateBuilder +import dev.kord.rest.builder.interaction.group +import dev.kord.rest.builder.interaction.subCommand import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList @@ -49,9 +49,6 @@ public open class SlashCommandRegistry : KoinComponent { /** @suppress **/ public open val commandMap: MutableMap> = mutableMapOf() - /** @suppress **/ - public open val api: SlashCommands get() = kord.slashCommands - // TODO: Sentry? // private val sentry: SentryAdapter by bot.koin.inject() @@ -138,9 +135,9 @@ public open class SlashCommandRegistry : KoinComponent { val registered = commands[guild]!! val existing = if (guild == null) { - api.getGlobalApplicationCommands().map { Pair(it.name, it.id) }.toList() + kord.globalCommands.map { Pair(it.name, it.id) }.toList() } else { - api.getGuildApplicationCommands(guild).map { Pair(it.name, it.id) }.toList() + kord.unsafe.guild(guild).commands.map { Pair(it.name, it.id) }.toList() } if (!bot.settings.slashCommandsBuilder.register) { @@ -171,7 +168,7 @@ public open class SlashCommandRegistry : KoinComponent { val toCreate = toAdd + toUpdate if (guild == null) { - val response = api.createGlobalApplicationCommands { + val response = kord.createGlobalApplicationCommands { toCreate.forEach { val translatedName = it.getTranslatedName(locale) @@ -188,7 +185,7 @@ public open class SlashCommandRegistry : KoinComponent { commandMap[response[it.getTranslatedName(locale)]!!] = it } - api.getGlobalApplicationCommands().filter { e -> toRemove.any { it.second == e.id } } + kord.globalCommands.filter { e -> toRemove.any { it.second == e.id } } .toList() .forEach { logger.debug { "Removing global slash command ${it.name}" } @@ -196,7 +193,7 @@ public open class SlashCommandRegistry : KoinComponent { } } else { toCreate.groupBy { it.guild!! }.forEach { (snowflake, commands) -> - val response = api.createGuildApplicationCommands(snowflake) { + val response = kord.createGuildApplicationCommands(snowflake) { commands.forEach { val translatedName = it.getTranslatedName(locale) @@ -214,7 +211,7 @@ public open class SlashCommandRegistry : KoinComponent { } } - api.getGuildApplicationCommands(guild).filter { e -> toRemove.any { it.second == e.id } } + kord.unsafe.guild(guild).commands.filter { e -> toRemove.any { it.second == e.id } } .toList() .forEach { logger.debug { "Removing guild slash command ${it.name}" } @@ -228,7 +225,7 @@ public open class SlashCommandRegistry : KoinComponent { commandsWithPerms.forEach { (guild, commands) -> if (guild != null) { - api.bulkEditApplicationCommandPermissions(api.applicationId, guild) { + kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guild) { commands.forEach { (id, commandObj) -> command(id) { commandObj.allowedUsers.map { user(it, true) } @@ -331,11 +328,9 @@ public open class SlashCommandRegistry : KoinComponent { } } - /** Handle an [InteractionCreateEvent] and try to execute the corresponding command. **/ + /** Handle a [ChatInputCommandInteractionCreateEvent] and try to execute the corresponding command. **/ public open suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { - val interaction = event.interaction as? CommandInteraction ?: return - - val commandId = interaction.command.rootId + val commandId = event.interaction.command.rootId val command = commandMap[commandId] if (command == null) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Permissions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Permissions.kt index d938b244d9..23028eb810 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Permissions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Permissions.kt @@ -37,13 +37,15 @@ public fun Permission.toTranslationKey(): String = when (this) { Permission.Speak -> "permission.speak" Permission.Stream -> "permission.stream" Permission.UseExternalEmojis -> "permission.useExternalEmojis" - Permission.UsePrivateThreads -> "permission.usePrivateThreads" - Permission.UsePublicThreads -> "permission.usePublicThreads" Permission.UseSlashCommands -> "permission.useSlashCommands" Permission.UseVAD -> "permission.useVAD" Permission.ViewAuditLog -> "permission.viewAuditLog" Permission.ViewChannel -> "permission.viewChannel" Permission.ViewGuildInsights -> "permission.viewGuildInsights" + + Permission.CreatePublicThreads -> "permission.createPublicThreads" + Permission.CreatePrivateThreads -> "permission.createPrivateThreads" + Permission.SendMessagesInThreads -> "permission.sendMessagesInThreads" } /** Because "Stream" is a confusing name, people may look for "Video" instead. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt index 7ea0ea3fc0..6c51b7bfa8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Translation.kt @@ -4,8 +4,7 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot import dev.kord.common.annotation.KordPreview import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.event.Event -import dev.kord.core.event.interaction.ApplicationCreateEvent -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import java.util.* @@ -36,43 +35,9 @@ public suspend fun MessageCreateEvent.getLocale(): Locale { return result } -/** Attempt to resolve the locale for the given [ApplicationCreateEvent] object. **/ +/** Attempt to resolve the locale for the given [InteractionCreateEvent] object. **/ @OptIn(KordPreview::class) -public suspend fun ApplicationCreateEvent.getLocale(): Locale { - val existing = localeCache[this] - - if (existing != null) { - return existing - } - - val bot = getKoin().get() - var result = bot.settings.i18nBuilder.defaultLocale - - for (resolver in bot.settings.i18nBuilder.localeResolvers) { - val channel = interaction.channel.asChannel() - - val guild = if (channel is GuildChannel) { - channel.guild - } else { - null - } - - val resolved = resolver(guild, interaction.channel, interaction.user) - - if (resolved != null) { - result = resolved - break - } - } - - localeCache[this] = result - - return result -} - -/** Attempt to resolve the locale for the given [ComponentCreateEvent] object. **/ -@OptIn(KordPreview::class) -public suspend fun ComponentCreateEvent.getLocale(): Locale { +public suspend fun InteractionCreateEvent.getLocale(): Locale { val existing = localeCache[this] if (existing != null) { diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 195225de16..34710cff8a 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -152,6 +152,8 @@ permission.banMembers=Ban Members permission.changeNickname=Change Nickname permission.connect=Connect (Voice) permission.createInstantInvite=Create Invite +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Deafen Members permission.embedLinks=Embed Links permission.kickMembers=Kick Members @@ -170,12 +172,11 @@ permission.prioritySpeaker=Priority Speaker permission.readMessageHistory=Read Message History permission.requestToSpeak=Request to Speak permission.sendMessages=Send Messages +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Send TTS Messages permission.speak=Speak (Voice) permission.stream=Video permission.useExternalEmojis=Use External Emojis -permission.usePrivateThreads=View Server Insights -permission.usePublicThreads=View Server Insights permission.useSlashCommands=Use Slash Commands permission.useVAD=Use Voice Activity permission.viewAuditLog=View Audit Log diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index 10f6c82296..47db791f4e 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -100,15 +100,18 @@ permission.banMembers=Ban Members permission.changeNickname=Change Nickname permission.connect=Connect (Voice) permission.createInstantInvite=Create Invite +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Deafen Members permission.embedLinks=Embed Links permission.kickMembers=Kick Members permission.manageChannels=Manage Channels permission.manageEmojis=Manage Emojis -permission.manageGuild=Manage +permission.manageGuild=Manage Server permission.manageMessages=Manage Messages permission.manageNicknames=Manage Nicknames permission.manageRoles=Manage Roles +permission.manageThreads=View Server Insights permission.manageWebhooks=Manage Webhooks permission.mentionEveryone=Mention Everyone permission.moveMembers=Move Members @@ -117,6 +120,7 @@ permission.prioritySpeaker=Priority Speaker permission.readMessageHistory=Read Message History permission.requestToSpeak=Request to Speak permission.sendMessages=Send Messages +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Send TTS Messages permission.speak=Speak (Voice) permission.stream=Video From bf3708f066c05e3baa6f7c6e281dd09bf67f10aa Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 22 Aug 2021 22:03:37 +0100 Subject: [PATCH 007/131] Extra paginator builders in PaginatorBuilder. PaginatorBuilder paginator builders. Heh. --- kord-extensions/build.gradle.kts | 7 + .../pagination/builders/PaginatorBuilder.kt | 16 ++ .../kord/extensions/test/bot/TestExtension.kt | 155 ++++++++---------- 3 files changed, 94 insertions(+), 84 deletions(-) diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts index 9235379a9b..b44a1a1443 100644 --- a/kord-extensions/build.gradle.kts +++ b/kord-extensions/build.gradle.kts @@ -1,5 +1,6 @@ import java.io.ByteArrayOutputStream import java.net.URL +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { repositories { @@ -195,3 +196,9 @@ tasks.test { tasks.build { this.finalizedBy(sourceJar, javadocJar) } + +val compileKotlin: KotlinCompile by tasks + +compileKotlin.kotlinOptions { + languageVersion = "1.5" +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt index e33c4e8902..3102b26216 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt @@ -5,6 +5,7 @@ import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.core.entity.ReactionEmoji import dev.kord.core.entity.User +import dev.kord.rest.builder.message.EmbedBuilder import java.util.* /** @@ -42,4 +43,19 @@ public class PaginatorBuilder( /** Add a page to [pages], using the given group. **/ public fun page(group: String, page: Page): Unit = pages.addPage(group, page) + + /** Add a page to [pages], using the default group. **/ + public fun page( + bundle: String? = null, + builder: suspend EmbedBuilder.() -> Unit + ): Unit = + page(Page(builder = builder, bundle = bundle)) + + /** Add a page to [pages], using the given group. **/ + public fun page( + group: String, + bundle: String? = null, + builder: suspend EmbedBuilder.() -> Unit + ): Unit = + page(group, Page(builder = builder, bundle = bundle)) } diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index cdc2c7e42c..4cf76004fd 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -11,7 +11,6 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.extensions.chatGroupCommand import com.kotlindiscord.kord.extensions.extensions.slashCommand -import com.kotlindiscord.kord.extensions.pagination.InteractionButtonPaginator import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages @@ -156,59 +155,52 @@ class TestExtension : Extension() { guild(787452339908116521) action { - val pages = Pages() + paginator("short") { + owner = event.interaction.user.asUser() + timeoutSeconds = 60 + keepEmbed = false - (0..2).forEach { - pages.addPage( - Page { - description = "Short page $it." + (0..2).forEach { + page( + Page { + description = "Short page $it." - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) + ) - pages.addPage( - "Expanded", + page( + "Expanded", - Page { - description = "Expanded page $it, expanded page $it\n" + - "Expanded page $it, expanded page $it" + Page { + description = "Expanded page $it, expanded page $it\n" + + "Expanded page $it, expanded page $it" - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) + ) - pages.addPage( - "MASSIVE GROUP", + page( + "MASSIVE GROUP", - Page { - description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it" + Page { + description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it" - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) - } - - val paginator = InteractionButtonPaginator( - extension = this@TestExtension, - pages = pages, - owner = event.interaction.user.asUser(), - timeoutSeconds = 60, - parentContext = this, - keepEmbed = false - ) - - paginator.send() + ) + } + }.send() } } @@ -515,59 +507,54 @@ class TestExtension : Extension() { description = "Paginator test" action { - val pages = Pages(defaultGroup = "short") + paginator("short", targetMessage = event.message) { + keepEmbed = true + owner = user + locale = getLocale() - (0..2).forEach { - pages.addPage( - "short", + (0..2).forEach { + page( + "short", - Page { - description = "Short page $it." + Page { + description = "Short page $it." - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) + ) - pages.addPage( - "expanded", + page( + "expanded", - Page { - description = "Expanded page $it, expanded page $it\n" + - "Expanded page $it, expanded page $it" + Page { + description = "Expanded page $it, expanded page $it\n" + + "Expanded page $it, expanded page $it" - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) + ) - pages.addPage( - "massive", + page( + "massive", - Page { - description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it" + Page { + description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it" - footer { - text = "Footer text ($it)" + footer { + text = "Footer text ($it)" + } } - } - ) - } - - MessageButtonPaginator( - extension = this@TestExtension, - targetMessage = event.message, - pages = pages, - keepEmbed = true, - owner = user, - locale = getLocale() - ).send() + ) + } + }.send() } } From 295a53f2b6f324269f7afe7355f2bff62b7a531d Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 22 Aug 2021 23:22:22 +0100 Subject: [PATCH 008/131] Quick fix for inconsistent check output for slash commands --- .../kord/extensions/commands/slash/SlashCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt index f5bea2669d..dc28245caa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt @@ -451,7 +451,7 @@ public open class SlashCommand( val message = context.message if (message != null && sendMessage) { - if (autoAck == AutoAckType.EPHEMERAL) { + if (autoAck != AutoAckType.PUBLIC) { event.interaction.respondEphemeral { content = translationsProvider.translate( "checks.responseTemplate", From 7d649a2fd314068c3817f621326a51698bb18072 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 23 Aug 2021 16:28:08 +0100 Subject: [PATCH 009/131] Generic checks to allow for events in callbacks --- .../ext/common/extensions/EmojiExtension.kt | 2 +- .../kord/extensions/checks/ChannelChecks.kt | 64 +++++++++---------- .../extensions/checks/CheckCombinators.kt | 9 +-- .../kord/extensions/checks/GuildChecks.kt | 17 +++-- .../kord/extensions/checks/RoleChecks.kt | 64 +++++++++---------- .../extensions/checks/TopChannelChecks.kt | 16 ++--- 6 files changed, 86 insertions(+), 86 deletions(-) diff --git a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt index a60ba0d400..a8b697f4bb 100644 --- a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt +++ b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt @@ -33,7 +33,7 @@ class EmojiExtension : Extension() { { config.getGuilds() .mapNotNull { kord.getGuild(it) } - .map { inGuild { it } } + .map { guild -> inGuild { guild } } .any() } ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt index c6c4cf7cca..7852b87e11 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt @@ -21,7 +21,7 @@ import mu.KotlinLogging * * @param builder Lambda returning the channel to compare to. */ -public fun inChannel(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun inChannel(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val eventChannel = channelFor(event) @@ -30,7 +30,7 @@ public fun inChannel(builder: suspend () -> ChannelBehavior): Check<*> = { fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel.id == channel.id) { logger.passed() @@ -57,7 +57,7 @@ public fun inChannel(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param builder Lambda returning the channel to compare to. */ -public fun notInChannel(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun notInChannel(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val eventChannel = channelFor(event) @@ -66,7 +66,7 @@ public fun notInChannel(builder: suspend () -> ChannelBehavior): Check<*> = { pass() } else { - val channel = builder() + val channel = builder(event) if (eventChannel.id != channel.id) { logger.passed() @@ -93,7 +93,7 @@ public fun notInChannel(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param builder Lambda returning the category to compare to. */ -public fun inCategory(builder: suspend () -> CategoryBehavior): Check<*> = { +public fun inCategory(builder: suspend (T) -> CategoryBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inCategory") val eventChannel = topChannelFor(event) @@ -102,7 +102,7 @@ public fun inCategory(builder: suspend () -> CategoryBehavior): Check<*> = { fail() } else { - val category = builder() + val category = builder(event) val channels = category.channels.toList().map { it.id } if (channels.contains(eventChannel.id)) { @@ -130,7 +130,7 @@ public fun inCategory(builder: suspend () -> CategoryBehavior): Check<*> = { * * @param builder Lambda returning the category to compare to. */ -public fun notInCategory(builder: suspend () -> CategoryBehavior): Check<*> = { +public fun notInCategory(builder: suspend (T) -> CategoryBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInCategory") val eventChannel = topChannelFor(event) @@ -139,7 +139,7 @@ public fun notInCategory(builder: suspend () -> CategoryBehavior): Check<*> = { pass() } else { - val category = builder() + val category = builder(event) val channels = category.channels.toList().map { it.id } if (channels.contains(eventChannel.id)) { @@ -167,7 +167,7 @@ public fun notInCategory(builder: suspend () -> CategoryBehavior): Check<*> = { * * @param builder Lambda returning the channel to compare to. */ -public fun channelHigher(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun channelHigher(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigher") val eventChannel = channelFor(event) @@ -176,7 +176,7 @@ public fun channelHigher(builder: suspend () -> ChannelBehavior): Check<*> = { fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel > channel) { logger.passed() @@ -203,7 +203,7 @@ public fun channelHigher(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param builder Lambda returning the channel to compare to. */ -public fun channelLower(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun channelLower(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLower") val eventChannel = channelFor(event) @@ -212,7 +212,7 @@ public fun channelLower(builder: suspend () -> ChannelBehavior): Check<*> = { fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel < channel) { logger.passed() @@ -239,7 +239,7 @@ public fun channelLower(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param builder Lambda returning the channel to compare to. */ -public fun channelHigherOrEqual(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun channelHigherOrEqual(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigherOrEqual") val eventChannel = channelFor(event) @@ -248,7 +248,7 @@ public fun channelHigherOrEqual(builder: suspend () -> ChannelBehavior): Check<* fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel >= channel) { logger.passed() @@ -275,7 +275,7 @@ public fun channelHigherOrEqual(builder: suspend () -> ChannelBehavior): Check<* * * @param builder Lambda returning the channel to compare to. */ -public fun channelLowerOrEqual(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun channelLowerOrEqual(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLowerOrEqual") val eventChannel = channelFor(event) @@ -284,7 +284,7 @@ public fun channelLowerOrEqual(builder: suspend () -> ChannelBehavior): Check<*> fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel <= channel) { logger.passed() @@ -316,7 +316,7 @@ public fun channelLowerOrEqual(builder: suspend () -> ChannelBehavior): Check<*> * * @param id Channel snowflake to compare to. */ -public fun inChannel(id: Snowflake): Check<*> = { +public fun inChannel(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val channel = event.kord.getChannel(id) @@ -325,7 +325,7 @@ public fun inChannel(id: Snowflake): Check<*> = { fail() } else { - inChannel { channel }() + inChannel { channel }() } } @@ -337,7 +337,7 @@ public fun inChannel(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun notInChannel(id: Snowflake): Check<*> = { +public fun notInChannel(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val channel = event.kord.getChannel(id) @@ -346,7 +346,7 @@ public fun notInChannel(id: Snowflake): Check<*> = { pass() } else { - notInChannel { channel }() + notInChannel { channel }() } } @@ -358,7 +358,7 @@ public fun notInChannel(id: Snowflake): Check<*> = { * * @param id Category snowflake to compare to. */ -public fun inCategory(id: Snowflake): Check<*> = { +public fun inCategory(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inCategory") val category = event.kord.getChannelOf(id) @@ -367,7 +367,7 @@ public fun inCategory(id: Snowflake): Check<*> = { fail() } else { - inCategory { category }() + inCategory { category }() } } @@ -379,7 +379,7 @@ public fun inCategory(id: Snowflake): Check<*> = { * * @param id Category snowflake to compare to. */ -public fun notInCategory(id: Snowflake): Check<*> = { +public fun notInCategory(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInCategory") val category = event.kord.getChannelOf(id) @@ -388,7 +388,7 @@ public fun notInCategory(id: Snowflake): Check<*> = { pass() } else { - notInCategory { category }() + notInCategory { category }() } } @@ -400,7 +400,7 @@ public fun notInCategory(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun channelHigher(id: Snowflake): Check<*> = { +public fun channelHigher(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigher") val channel = event.kord.getChannel(id) @@ -409,7 +409,7 @@ public fun channelHigher(id: Snowflake): Check<*> = { fail() } else { - channelHigher { channel }() + channelHigher { channel }() } } @@ -421,7 +421,7 @@ public fun channelHigher(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun channelLower(id: Snowflake): Check<*> = { +public fun channelLower(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLower") val channel = event.kord.getChannel(id) @@ -430,7 +430,7 @@ public fun channelLower(id: Snowflake): Check<*> = { fail() } else { - channelLower { channel }() + channelLower { channel }() } } @@ -442,7 +442,7 @@ public fun channelLower(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun channelHigherOrEqual(id: Snowflake): Check<*> = { +public fun channelHigherOrEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigherOrEqual") val channel = event.kord.getChannel(id) @@ -451,7 +451,7 @@ public fun channelHigherOrEqual(id: Snowflake): Check<*> = { fail() } else { - channelHigherOrEqual { channel }() + channelHigherOrEqual { channel }() } } @@ -463,7 +463,7 @@ public fun channelHigherOrEqual(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun channelLowerOrEqual(id: Snowflake): Check<*> = { +public fun channelLowerOrEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLowerOrEqual") val channel = event.kord.getChannel(id) @@ -472,7 +472,7 @@ public fun channelLowerOrEqual(id: Snowflake): Check<*> = { fail() } else { - channelLowerOrEqual { channel }() + channelLowerOrEqual { channel }() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt index 5d3d56150a..648115d5ee 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt @@ -2,6 +2,7 @@ package com.kotlindiscord.kord.extensions.checks import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import dev.kord.core.event.Event import mu.KotlinLogging /** @@ -13,7 +14,7 @@ import mu.KotlinLogging * @param checks Two or more checks to combine. * @return Whether any of the checks passed. */ -public fun or(vararg checks: Check<*>): Check<*> = { +public fun or(vararg checks: Check): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.or") val contexts = checks.map { @@ -41,7 +42,7 @@ public fun or(vararg checks: Check<*>): Check<*> = { } /** Infix-function version of [or]. **/ -public infix fun (Check<*>).or(other: Check<*>): Check<*> = or(this, other) +public infix fun (Check).or(other: Check): Check = or(this, other) /** * Special check that passes if all of the given checks pass. @@ -55,7 +56,7 @@ public infix fun (Check<*>).or(other: Check<*>): Check<*> = or(this, other) * @param checks Two or more checks to combine. * @return Whether all of the checks passed. */ -public fun and(vararg checks: Check<*>): Check<*> = { +public fun and(vararg checks: Check): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.and") val contexts = checks.map { @@ -83,4 +84,4 @@ public fun and(vararg checks: Check<*>): Check<*> = { } /** Infix-function version of [and]. **/ -public infix fun (Check<*>).and(other: Check<*>): Check<*> = and(this, other) +public infix fun (Check).and(other: Check): Check = and(this, other) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt index 68075b758b..87f11dbee8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt @@ -7,7 +7,6 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.GuildBehavior import dev.kord.core.event.Event import mu.KotlinLogging -import java.util.* /** * Check asserting an [Event] was fired within a guild. @@ -65,7 +64,7 @@ public val noGuild: Check<*> = { * * @param builder Lambda returning the guild to compare to. */ -public fun inGuild(builder: suspend () -> GuildBehavior): Check<*> = { +public fun inGuild(builder: suspend (T) -> GuildBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inGuild") val eventGuild = guildFor(event)?.asGuildOrNull() @@ -74,7 +73,7 @@ public fun inGuild(builder: suspend () -> GuildBehavior): Check<*> = { fail() } else { - val guild = builder() + val guild = builder(event) if (eventGuild.id == guild.id) { logger.passed() @@ -101,7 +100,7 @@ public fun inGuild(builder: suspend () -> GuildBehavior): Check<*> = { * * @param builder Lambda returning the guild to compare to. */ -public fun notInGuild(builder: suspend () -> GuildBehavior): Check<*> = { +public fun notInGuild(builder: suspend (T) -> GuildBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInGuild") val eventGuild = guildFor(event)?.asGuild() @@ -110,7 +109,7 @@ public fun notInGuild(builder: suspend () -> GuildBehavior): Check<*> = { pass() } else { - val guild = builder() + val guild = builder(event) if (eventGuild.id != guild.id) { logger.passed() @@ -141,7 +140,7 @@ public fun notInGuild(builder: suspend () -> GuildBehavior): Check<*> = { * * @param id Guild snowflake to compare to. */ -public fun inGuild(id: Snowflake): Check<*> = { +public fun inGuild(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inGuild") val guild = event.kord.getGuild(id) @@ -150,7 +149,7 @@ public fun inGuild(id: Snowflake): Check<*> = { fail() } else { - inGuild { guild }() + inGuild { guild }() } } @@ -162,7 +161,7 @@ public fun inGuild(id: Snowflake): Check<*> = { * * @param id Guild snowflake to compare to. */ -public fun notInGuild(id: Snowflake): Check<*> = { +public fun notInGuild(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInGuild") val guild = event.kord.getGuild(id) @@ -171,7 +170,7 @@ public fun notInGuild(id: Snowflake): Check<*> = { pass() } else { - notInGuild { guild }() + notInGuild { guild }() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt index bd1a943f4e..e351f5bf64 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt @@ -20,7 +20,7 @@ import mu.KotlinLogging * * @param builder Lambda returning the role to compare to. */ -public fun hasRole(builder: suspend () -> RoleBehavior): Check<*> = { +public fun hasRole(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.hasRole") val member = memberFor(event) @@ -29,7 +29,7 @@ public fun hasRole(builder: suspend () -> RoleBehavior): Check<*> = { fail() } else { - val role = builder() + val role = builder(event) if (member.asMember().roles.toList().contains(role)) { logger.passed() @@ -56,7 +56,7 @@ public fun hasRole(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun notHasRole(builder: suspend () -> RoleBehavior): Check<*> = { +public fun notHasRole(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notHasRole") val member = memberFor(event) @@ -65,7 +65,7 @@ public fun notHasRole(builder: suspend () -> RoleBehavior): Check<*> = { pass() } else { - val role = builder() + val role = builder(event) if (member.asMember().roles.toList().contains(role)) { logger.failed("Member $member has role $role") @@ -92,7 +92,7 @@ public fun notHasRole(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun topRoleEqual(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleEqual(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleEqual") val member = memberFor(event) @@ -101,7 +101,7 @@ public fun topRoleEqual(builder: suspend () -> RoleBehavior): Check<*> = { fail() } else { - val role = builder() + val role = builder(event) val topRole = member.asMember().getTopRole() when { @@ -144,7 +144,7 @@ public fun topRoleEqual(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun topRoleNotEqual(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleNotEqual(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleNotEqual") val member = memberFor(event) @@ -153,7 +153,7 @@ public fun topRoleNotEqual(builder: suspend () -> RoleBehavior): Check<*> = { pass() } else { - val role = builder() + val role = builder(event) when (member.asMember().getTopRole()) { null -> { @@ -190,7 +190,7 @@ public fun topRoleNotEqual(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun topRoleHigher(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleHigher(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigher") val member = memberFor(event) @@ -199,7 +199,7 @@ public fun topRoleHigher(builder: suspend () -> RoleBehavior): Check<*> = { fail() } else { - val role = builder() + val role = builder(event) val topRole = member.asMember().getTopRole() when { @@ -244,7 +244,7 @@ public fun topRoleHigher(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun topRoleLower(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleLower(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLower") val member = memberFor(event) @@ -253,7 +253,7 @@ public fun topRoleLower(builder: suspend () -> RoleBehavior): Check<*> = { fail() } else { - val role = builder() + val role = builder(event) val topRole = member.asMember().getTopRole() when { @@ -292,7 +292,7 @@ public fun topRoleLower(builder: suspend () -> RoleBehavior): Check<*> = { * * @param builder Lambda returning the role to compare to. */ -public fun topRoleHigherOrEqual(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleHigherOrEqual(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigherOrEqual") val member = memberFor(event) @@ -301,7 +301,7 @@ public fun topRoleHigherOrEqual(builder: suspend () -> RoleBehavior): Check<*> = fail() } else { - val role = builder() + val role = builder(event) val topRole = member.asMember().getTopRole() when { @@ -347,7 +347,7 @@ public fun topRoleHigherOrEqual(builder: suspend () -> RoleBehavior): Check<*> = * * @param builder Lambda returning the role to compare to. */ -public fun topRoleLowerOrEqual(builder: suspend () -> RoleBehavior): Check<*> = { +public fun topRoleLowerOrEqual(builder: suspend (T) -> RoleBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLowerOrEqual") val member = memberFor(event) @@ -356,7 +356,7 @@ public fun topRoleLowerOrEqual(builder: suspend () -> RoleBehavior): Check<*> = fail() } else { - val role = builder() + val role = builder(event) val topRole = member.asMember().getTopRole() when { @@ -398,7 +398,7 @@ public fun topRoleLowerOrEqual(builder: suspend () -> RoleBehavior): Check<*> = * * @param id Role snowflake to compare to. */ -public fun hasRole(id: Snowflake): Check<*> = { +public fun hasRole(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.hasRole") val role = guildFor(event)?.getRoleOrNull(id) @@ -407,7 +407,7 @@ public fun hasRole(id: Snowflake): Check<*> = { fail() } else { - hasRole { role }() + hasRole { role }() } } @@ -419,7 +419,7 @@ public fun hasRole(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun notHasRole(id: Snowflake): Check<*> = { +public fun notHasRole(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notHasRole") val role = guildFor(event)?.getRoleOrNull(id) @@ -428,7 +428,7 @@ public fun notHasRole(id: Snowflake): Check<*> = { pass() } else { - notHasRole { role }() + notHasRole { role }() } } @@ -440,7 +440,7 @@ public fun notHasRole(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleEqual(id: Snowflake): Check<*> = { +public fun topRoleEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -449,7 +449,7 @@ public fun topRoleEqual(id: Snowflake): Check<*> = { fail() } else { - topRoleEqual { role }() + topRoleEqual { role }() } } @@ -461,7 +461,7 @@ public fun topRoleEqual(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleNotEqual(id: Snowflake): Check<*> = { +public fun topRoleNotEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleNotEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -470,7 +470,7 @@ public fun topRoleNotEqual(id: Snowflake): Check<*> = { pass() } else { - topRoleNotEqual { role }() + topRoleNotEqual { role }() } } @@ -482,7 +482,7 @@ public fun topRoleNotEqual(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleHigher(id: Snowflake): Check<*> = { +public fun topRoleHigher(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigher") val role = guildFor(event)?.getRoleOrNull(id) @@ -491,7 +491,7 @@ public fun topRoleHigher(id: Snowflake): Check<*> = { fail() } else { - topRoleHigher { role }() + topRoleHigher { role }() } } @@ -505,7 +505,7 @@ public fun topRoleHigher(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleLower(id: Snowflake): Check<*> = { +public fun topRoleLower(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLower") val role = guildFor(event)?.getRoleOrNull(id) @@ -514,7 +514,7 @@ public fun topRoleLower(id: Snowflake): Check<*> = { fail() } else { - topRoleLower { role }() + topRoleLower { role }() } } @@ -527,7 +527,7 @@ public fun topRoleLower(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleHigherOrEqual(id: Snowflake): Check<*> = { +public fun topRoleHigherOrEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigherOrEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -536,7 +536,7 @@ public fun topRoleHigherOrEqual(id: Snowflake): Check<*> = { fail() } else { - topRoleHigherOrEqual { role }() + topRoleHigherOrEqual { role }() } } @@ -551,7 +551,7 @@ public fun topRoleHigherOrEqual(id: Snowflake): Check<*> = { * * @param id Role snowflake to compare to. */ -public fun topRoleLowerOrEqual(id: Snowflake): Check<*> = { +public fun topRoleLowerOrEqual(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLowerOrEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -560,7 +560,7 @@ public fun topRoleLowerOrEqual(id: Snowflake): Check<*> = { fail() } else { - topRoleLowerOrEqual { role }() + topRoleLowerOrEqual { role }() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt index 5891d5a4b4..06a967f0e1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt @@ -20,7 +20,7 @@ import mu.KotlinLogging * * @param builder Lambda returning the channel to compare to. */ -public fun inTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun inTopChannel(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val eventChannel = topChannelFor(event) @@ -29,7 +29,7 @@ public fun inTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { fail() } else { - val channel = builder() + val channel = builder(event) if (eventChannel.id == channel.id) { logger.passed() @@ -57,7 +57,7 @@ public fun inTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param builder Lambda returning the channel to compare to. */ -public fun notInTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { +public fun notInTopChannel(builder: suspend (T) -> ChannelBehavior): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val eventChannel = topChannelFor(event) @@ -66,7 +66,7 @@ public fun notInTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { pass() } else { - val channel = builder() + val channel = builder(event) if (eventChannel.id != channel.id) { logger.passed() @@ -98,7 +98,7 @@ public fun notInTopChannel(builder: suspend () -> ChannelBehavior): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun inTopChannel(id: Snowflake): Check<*> = { +public fun inTopChannel(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") var channel = event.kord.getChannel(id) @@ -111,7 +111,7 @@ public fun inTopChannel(id: Snowflake): Check<*> = { fail() } else { - inChannel { channel }() + inChannel { channel }() } } @@ -124,7 +124,7 @@ public fun inTopChannel(id: Snowflake): Check<*> = { * * @param id Channel snowflake to compare to. */ -public fun notInTopChannel(id: Snowflake): Check<*> = { +public fun notInTopChannel(id: Snowflake): Check = { val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") var channel = event.kord.getChannel(id) @@ -137,7 +137,7 @@ public fun notInTopChannel(id: Snowflake): Check<*> = { pass() } else { - notInChannel { channel }() + notInChannel { channel }() } } From db6363b85e58566eb5071c34eea21ee004575e1b Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 23 Aug 2021 17:00:56 +0100 Subject: [PATCH 010/131] Grouped chat commands: Peek for command name, parse later --- .../kord/extensions/commands/chat/ChatGroupCommand.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index 43da579b0a..9719eae11b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -203,13 +203,15 @@ public open class ChatGroupCommand( return } - val command = parser.parseNext()?.data?.lowercase() + val command = parser.peekNext()?.data?.lowercase() val remainingArgs = parser.consumeRemaining() val subCommand = getCommand(command, event) if (subCommand == null) { super.call(event, commandName, parser, argString, true) } else { + parser.parseNext() // Advance the cursor so proper parsing can happen + subCommand.call(event, commandName, StringParser(remainingArgs), argString) } } From a86283f08cb67e2079f42204a3e94c2c18c06a46 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 23 Aug 2021 22:47:40 +0100 Subject: [PATCH 011/131] Kord 0.8.0-M5 - rest request util tests are now impossible sadly --- .../kord/extensions/components/Components.kt | 4 +- .../builders/ActionableComponentBuilder.kt | 18 +-- .../components/builders/ComponentBuilder.kt | 4 +- .../builders/InteractiveButtonBuilder.kt | 4 +- .../components/builders/MenuBuilder.kt | 4 +- .../contexts/ActionableComponentContext.kt | 4 +- .../contexts/InteractiveButtonContext.kt | 4 +- .../components/contexts/MenuContext.kt | 4 +- .../pagination/BaseButtonPaginator.kt | 4 +- .../kord/extensions/utils/RestRequestTest.kt | 118 ------------------ libs.versions.toml | 2 +- 11 files changed, 26 insertions(+), 144 deletions(-) delete mode 100644 kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/utils/RestRequestTest.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt index 87d34bd198..54055ed785 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt @@ -11,7 +11,7 @@ import com.kotlindiscord.kord.extensions.extensions.event import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.create.actionRow import dev.kord.rest.builder.message.modify.MessageModifyBuilder @@ -50,7 +50,7 @@ public open class Components( public val kord: Kord by inject() /** Current event handler instance waiting for interaction creation events. **/ - public var eventHandler: EventHandler? = null + public var eventHandler: EventHandler? = null /** @suppress Internal Job object representing the timeout job. **/ public var delayJob: Job? = null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt index c7352bcc67..499a28e96a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt @@ -24,7 +24,7 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import io.sentry.Sentry import io.sentry.protocol.SentryId import mu.KotlinLogging @@ -71,7 +71,7 @@ public abstract class ActionableComponentBuilder> = mutableListOf() + public open val checks: MutableList> = mutableListOf() /** @suppress Internal variable, the click action to run. **/ public open lateinit var body: suspend R.() -> Unit @@ -83,11 +83,11 @@ public abstract class ActionableComponentBuilder): Boolean = + public open fun check(vararg checks: Check): Boolean = this.checks.addAll(checks) /** Register a check that must pass for this button to be actioned. **/ - public open fun check(check: Check): Boolean = + public open fun check(check: Check): Boolean = checks.add(check) /** @@ -97,7 +97,7 @@ public abstract class ActionableComponentBuilder Boolean) { + public open fun booleanCheck(vararg checks: suspend (ComponentInteractionCreateEvent) -> Boolean) { checks.forEach(::booleanCheck) } @@ -108,7 +108,7 @@ public abstract class ActionableComponentBuilder Boolean) { + public open fun booleanCheck(check: suspend (ComponentInteractionCreateEvent) -> Boolean) { check { if (check(event)) { pass() @@ -124,7 +124,7 @@ public abstract class ActionableComponentBuilder? ) { if (!runChecks(event)) { @@ -280,7 +280,7 @@ public abstract class ActionableComponentBuilder? = null ) { throw UnsupportedOperationException("This type of component doesn't support callable actions.") diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt index d4ee357673..ba8d1c0725 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt @@ -10,7 +10,7 @@ import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.DiscordPartialEmoji import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.entity.interaction.ButtonInteraction -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder import mu.KotlinLogging @@ -52,7 +52,7 @@ public open class InteractiveButtonBuilder : ButtonBuilder, override fun getContext( extension: Extension, - event: ComponentCreateEvent, + event: ComponentInteractionCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior?, interaction: ButtonInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt index 860ed51c14..01f37c546f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder import dev.kord.rest.builder.component.SelectOptionBuilder @@ -91,7 +91,7 @@ public open class MenuBuilder : ActionableComponentBuilder( public open val extension: Extension, - public open val event: ComponentCreateEvent, + public open val event: ComponentInteractionCreateEvent, public open val components: Components, public open var interactionResponse: InteractionResponseBehavior? = null, public open val interaction: T = event.interaction as T diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt index 33e28b9ca3..92d5c6cc7d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.* -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import org.koin.core.component.KoinComponent /** @@ -16,7 +16,7 @@ import org.koin.core.component.KoinComponent @ExtensionDSL public open class InteractiveButtonContext( extension: Extension, - event: ComponentCreateEvent, + event: ComponentInteractionCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, interaction: ButtonInteraction = event.interaction as ButtonInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt index 75253905a2..3caab36151 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import org.koin.core.component.KoinComponent import java.util.* @@ -17,7 +17,7 @@ import java.util.* @ExtensionDSL public open class MenuContext( extension: Extension, - event: ComponentCreateEvent, + event: ComponentInteractionCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, interaction: SelectMenuInteraction = event.interaction as SelectMenuInteraction diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index 8b737d0d99..ca3391a182 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -13,7 +13,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ButtonStyle import dev.kord.core.entity.ReactionEmoji import dev.kord.core.entity.User -import dev.kord.core.event.interaction.ComponentCreateEvent +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import java.util.* /** Last row number. **/ @@ -61,7 +61,7 @@ public abstract class BaseButtonPaginator( public val canUseSwitchingButtons: Boolean = allGroups.size in 3..5 && "" !in allGroups /** A button-oriented check function that matches based on the [owner] property. **/ - public val defaultCheck: Check = { + public val defaultCheck: Check = { if (!active) { fail() } else if (owner == null) { diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/utils/RestRequestTest.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/utils/RestRequestTest.kt deleted file mode 100644 index b654dab240..0000000000 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/utils/RestRequestTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.kotlindiscord.kord.extensions.utils - -import dev.kord.rest.request.HttpStatus -import dev.kord.rest.request.RestRequestException -import io.ktor.http.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.parallel.Execution -import org.junit.jupiter.api.parallel.ExecutionMode - -/** - * Tests for [RestRequestException] extension functions. - */ -class RestRequestTest { - - /** - * Mock class implementing [RestRequestException]. - */ - private inner class RequestMockException(status: HttpStatus) : RestRequestException(status) - - /** - * Test `hasStatus()` with zero parameters. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasStatus with zero parameters`() { - createMockAndHasStatus(HttpStatusCode.Forbidden, false) - } - - /** - * Test `hasStatus()` with one parameter. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasStatus with one parameter`() { - createMockAndHasStatus(HttpStatusCode.Forbidden, true, HttpStatusCode.Forbidden) - } - - /** - * Test `hasStatus()` with multiple parameters. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasStatus with multiple parameters`() { - createMockAndHasStatus( - HttpStatusCode.Forbidden, - true, - HttpStatusCode.BadRequest, - HttpStatusCode.Forbidden, - HttpStatusCode.Accepted - ) - } - - /** - * Test `hasNotStatus()` with zero parameters. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasNotStatus with zero parameters`() { - createMockAndHasNotStatus(HttpStatusCode.Forbidden, true) - } - - /** - * Test `hasNotStatus()` with one parameter. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasNotStatus with one parameter`() { - createMockAndHasNotStatus(HttpStatusCode.Forbidden, false, HttpStatusCode.Forbidden) - } - - /** - * Test `hasNotStatus()` with multiple parameters. - */ - @Test - @Execution(ExecutionMode.CONCURRENT) - fun `test hasNotStatus with multiple parameters`() { - createMockAndHasNotStatus( - HttpStatusCode.Forbidden, - false, - HttpStatusCode.BadRequest, - HttpStatusCode.Forbidden, - HttpStatusCode.Accepted - ) - } - - /** - * Create an instance of [RequestMockException] with the given [status] code, and check whether it has a matching - * status from [statuses] via `hasStatus()` and `hasStatusCode()`. - * - * @param status HTTP status code to be passed to the mock object. - * @param result The expected return value of `hasStatus()` and `hasStatusCode()`. - * @param statuses Status codes to check for. - */ - private fun createMockAndHasStatus(status: HttpStatusCode, result: Boolean, vararg statuses: HttpStatusCode) { - val code = status.value - val ex = RequestMockException(HttpStatus(code, "")) - - assertEquals(result, ex.hasStatus(*statuses)) - assertEquals(result, ex.hasStatusCode(*statuses.map { it.value }.toIntArray())) - } - - /** - * Create an instance of [RequestMockException] with the given [status] code, and check whether it - * **does not have** a matching status from [statuses] via `hasNotStatus()` and `hasNotStatusCode()`. - * - * @param status HTTP status code to be passed to the mock object. - * @param result The expected return value of `hasNotStatus()` and `hasNotStatusCode()`. - * @param statuses Status codes to check for. - */ - private fun createMockAndHasNotStatus(status: HttpStatusCode, result: Boolean, vararg statuses: HttpStatusCode) { - val code = status.value - val ex = RequestMockException(HttpStatus(code, "")) - - assertEquals(result, ex.hasNotStatus(*statuses)) - assertEquals(result, ex.hasNotStatusCode(*statuses.map { it.value }.toIntArray())) - } -} diff --git a/libs.versions.toml b/libs.versions.toml index 2bc878262e..a60b761fdc 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -11,7 +11,7 @@ junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" #kord = "0.8.0-M4" -kord = "contexts-SNAPSHOT" +kord = "0.8.0-M5" kotlinpoet = "1.8.0" ksp = "1.5.10-1.0.0-beta02" kx-ser = "1.2.1" From b027adf10936fd5c2607e6835a52d29c039ba697 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 24 Aug 2021 16:02:05 +0100 Subject: [PATCH 012/131] Sentry context, initial application command layout --- .../extensions/checks/types/CheckContext.kt | 12 + .../extensions/commands/CommandContext.kt | 33 +-- .../application/ApplicationCommand.kt | 240 ++++++++++++++++++ .../application/ApplicationCommandContext.kt | 4 + .../application/ApplicationCommandRegistry.kt | 24 ++ .../application/message/MessageCommand.kt | 14 + .../application/slash/SlashCommand.kt | 28 ++ .../application/slash/SlashCommandContext.kt | 6 + .../commands/application/user/UserCommand.kt | 14 + .../extensions/commands/chat/ChatCommand.kt | 68 ++--- .../converters/impl/DecimalConverter.kt | 4 +- .../extensions/commands/slash/SlashCommand.kt | 72 +++--- .../builders/ActionableComponentBuilder.kt | 74 +++--- .../builders/InteractiveButtonBuilder.kt | 6 +- .../components/builders/MenuBuilder.kt | 6 +- .../contexts/ActionableComponentContext.kt | 35 +-- .../contexts/InteractiveButtonContext.kt | 6 +- .../components/contexts/MenuContext.kt | 6 +- .../kord/extensions/events/EventContext.kt | 33 +-- .../kord/extensions/events/EventHandler.kt | 103 ++++---- .../extensions/impl/SentryExtension.kt | 12 +- .../kord/extensions/sentry/BreadcrumbType.kt | 39 +++ .../kord/extensions/sentry/Converters.kt | 2 + .../kord/extensions/sentry/SentryAdapter.kt | 30 +-- .../kord/extensions/sentry/SentryContext.kt | 208 +++++++++++++++ .../extensions/sentry/{Utils.kt => _Utils.kt} | 26 +- 26 files changed, 787 insertions(+), 318 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/BreadcrumbType.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/{Utils.kt => _Utils.kt} (69%) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index 954ce02326..e6698f5b6d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -1,10 +1,12 @@ package com.kotlindiscord.kord.extensions.checks.types +import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.core.event.Event import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* +import kotlin.jvm.Throws /** * Class representing the context for a check. This allows the storage of check status and a message for the users. @@ -94,4 +96,14 @@ public class CheckContext(public val event: T, public val locale: /** Quick access to translate strings using this check context's [locale]. **/ public fun translate(key: String, bundle: String? = null, replacements: Array = arrayOf()): String = translations.translate(key, locale, bundleName = bundle, replacements = replacements) + + /** If this check has failed and a message is set, throw a `CommandException` with the translated message. **/ + @Throws(CommandException::class) + public fun throwIfFailedWithMessage() { + if (passed.not() && message != null) { + throw CommandException( + translate("checks.responseTemplate", replacements = arrayOf(message)) + ) + } + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt index d25f290942..08b4fd89ff 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt @@ -6,15 +6,13 @@ import com.kotlindiscord.kord.extensions.checks.guildFor import com.kotlindiscord.kord.extensions.checks.userFor import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.parser.StringParser -import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import com.kotlindiscord.kord.extensions.sentry.SentryContext import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.event.Event -import io.sentry.Breadcrumb -import io.sentry.SentryLevel import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* @@ -40,11 +38,8 @@ public abstract class CommandContext( /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() - /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() - - /** A list of Sentry breadcrumbs created during command execution. **/ - public open val breadcrumbs: MutableList = mutableListOf() + /** Current Sentry context, containing breadcrumbs and other goodies. **/ + public val sentry: SentryContext = SentryContext() /** Cached locale variable, stored and retrieved by [getLocale]. **/ public open var resolvedLocale: Locale? = null @@ -67,28 +62,6 @@ public abstract class CommandContext( /** Extract user information from event data, if that context is available. **/ public abstract suspend fun getUser(): UserBehavior? - /** - * Add a Sentry breadcrumb to this command context. - * - * This should be used for the purposes of tracing what exactly is happening during your - * command processing. If the bot administrator decides to enable Sentry integration, the - * breadcrumbs will be sent to Sentry when there's a command processing error. - */ - public fun breadcrumb( - category: String? = null, - level: SentryLevel? = null, - message: String? = null, - type: String? = null, - - data: Map = mapOf() - ): Breadcrumb { - val crumb = sentry.createBreadcrumb(category, level, message, type, data) - - breadcrumbs.add(crumb) - - return crumb - } - /** Resolve the locale for this command context. **/ public suspend fun getLocale(): Locale { var locale: Locale? = resolvedLocale diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt new file mode 100644 index 0000000000..5fb185f570 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -0,0 +1,240 @@ +package com.kotlindiscord.kord.extensions.commands.application + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import com.kotlindiscord.kord.extensions.utils.getLocale +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.any +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.interaction.InteractionCreateEvent +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.util.* + +/** + * Abstract class representing an application command - extend this for actual implementations. + * + * @param extension Extension this application command belongs to. + */ +public abstract class ApplicationCommand( + public open val extension: Extension +) : KoinComponent { + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + + /** Kord instance, backing the ExtensibleBot. **/ + public val kord: Kord by inject() + + /** Sentry adapter, for easy access to Sentry functions. **/ + public val sentry: SentryAdapter by inject() + + /** @suppress **/ + public open val checkList: MutableList> = mutableListOf() + + /** @suppress **/ + public open var guildId: Snowflake? = null + + /** + * Whether to allow everyone to use this command by default. + * + * Leaving this at `true` means that your allowed roles/users sets will effectively be ignored, but your denied + * roles/users won't be. + * + * This will be set to `false` automatically by the `allowX` functions, to ensure that they're applied by Discord. + */ + public open var allowByDefault: Boolean = true + + /** + * List of allowed role IDs. Allows take priority over disallows. + */ + public open val allowedRoles: MutableSet = mutableSetOf() + + /** + * List of allowed invoker IDs. Allows take priority over disallows. + */ + public open val allowedUsers: MutableSet = mutableSetOf() + + /** + * List of disallowed role IDs. Allows take priority over disallows. + */ + public open val disallowedRoles: MutableSet = mutableSetOf() + + /** + * List of disallowed invoker IDs. Allows take priority over disallows. + */ + public open val disallowedUsers: MutableSet = mutableSetOf() + + /** Permissions required to be able to run this command. **/ + public open val requiredPerms: MutableSet = mutableSetOf() + + /** Translation cache, so we don't have to look up translations every time. **/ + public open val nameTranslationCache: MutableMap = mutableMapOf() + + /** Command name, shown on Discord. **/ + public lateinit var name: String + + /** Return this command's name translated for the given locale, cached as required. **/ + public open fun getTranslatedName(locale: Locale): String { + if (!nameTranslationCache.containsKey(locale)) { + nameTranslationCache[locale] = translationsProvider.translate( + this.name, + this.extension.bundle, + locale + ).lowercase() + } + + return nameTranslationCache[locale]!! + } + + /** If your bot requires permissions to be able to execute the command, add them using this function. **/ + public fun requirePermissions(vararg perms: Permission) { + perms.forEach { requiredPerms.add(it) } + } + + /** Specify a specific guild for this application command to be locked to. **/ + public open fun guild(guild: Snowflake) { + this.guildId = guild + } + + /** Specify a specific guild for this application command to be locked to. **/ + public open fun guild(guild: Long) { + this.guildId = Snowflake(guild) + } + + /** Specify a specific guild for this application command to be locked to. **/ + public open fun guild(guild: GuildBehavior) { + this.guildId = guild.id + } + + /** Register an allowed role, and set [allowByDefault] to `false`. **/ + public open fun allowRole(role: Snowflake) { + allowByDefault = false + + allowedRoles.add(role) + } + + /** Register an allowed role, and set [allowByDefault] to `false`. **/ + public open fun allowRole(role: UserBehavior): Unit = + allowRole(role.id) + + /** Register a disallowed role, and set [allowByDefault] to `false`. **/ + public open fun disallowRole(role: Snowflake) { + allowByDefault = false + + disallowedRoles.add(role) + } + + /** Register a disallowed role, and set [allowByDefault] to `false`. **/ + public open fun disallowRole(role: UserBehavior): Unit = + disallowRole(role.id) + + /** Register an allowed user, and set [allowByDefault] to `false`. **/ + public open fun allowUser(user: Snowflake): Boolean { + allowByDefault = false + + return allowedUsers.add(user) + } + + /** Register an allowed user, and set [allowByDefault] to `false`. **/ + public open fun allowUser(user: UserBehavior): Boolean = + allowUser(user.id) + + /** Register a disallowed user. **/ + public open fun disallowUser(user: Snowflake): Boolean = + disallowedUsers.add(user) + + /** Register a disallowed user. **/ + public open fun disallowUser(user: UserBehavior): Boolean = + disallowUser(user.id) + + /** + * Define a check which must pass for the command to be executed. + * + * A command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to this command. + */ + public open fun check(vararg checks: Check) { + checks.forEach { checkList.add(it) } + } + + /** + * Overloaded check function to allow for DSL syntax. + * + * @param check Check to apply to this command. + */ + public open fun check(check: Check) { + checkList.add(check) + } + + /** Override this in your subclass if you need to change how the command name is validated. **/ + public open fun validateName() { + if (::name.isInitialized.not() || name.isEmpty()) { + error("Application command names are required.") + } + } + + /** General command validation function. Can be overridden. **/ + public open fun validate() { + validateName() + } + + /** Called in order to execute the command. **/ + public open suspend fun doCall(event: E) { + } + + /** Runs standard checks that can be handled in a generic way, without worrying about subclass-specific checks. **/ + @Throws(CommandException::class) + public open suspend fun runStandardChecks(event: E): Boolean { + val locale = event.getLocale() + + // TODO: Global checks + // TODO: Extension-level checks + + checkList.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + // Handle discord-side perms checks, as they can't be relied on to enforce them + + val channel = event.interaction.channel.asChannelOrNull() as? GuildMessageChannel ?: return allowByDefault + val member = event.interaction.user.asMember(channel.guildId) + + val isAllowed = member.id in allowedUsers || member.roles.any { it.id in allowedRoles } + val isDenied = member.id in disallowedUsers || member.roles.any { it.id in disallowedRoles } + + return if (allowByDefault) { + !isDenied + } else { + isAllowed && !isDenied + } + } + + /** Override this in order to implement any subclass-specific checks. **/ + @Throws(CommandException::class) + public open suspend fun runChecks(event: E): Boolean = + runStandardChecks(event) + + /** Override this to implement the calling logic for your subclass. **/ + public abstract suspend fun call(event: E) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt new file mode 100644 index 0000000000..fa36e84d5d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -0,0 +1,4 @@ +package com.kotlindiscord.kord.extensions.commands.application + +/** Base class representing the shared functionality for an application command's context. **/ +public abstract class ApplicationCommandContext 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 new file mode 100644 index 0000000000..253c7496a3 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandRegistry.kt @@ -0,0 +1,24 @@ +package com.kotlindiscord.kord.extensions.commands.application + +import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import dev.kord.core.Kord +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** Registry for all Discord application commands. **/ +public open class ApplicationCommandRegistry : KoinComponent { + /** Current instance of the bot. **/ + public open val bot: ExtensibleBot by inject() + + /** Kord instance, backing the ExtensibleBot. **/ + public val kord: Kord by inject() + + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + + /** Register an application command. **/ + public open suspend fun register() { + TODO() + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt new file mode 100644 index 0000000000..477b32df88 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -0,0 +1,14 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** Message context command, for right-click actions on messages. **/ +public class MessageCommand( + extension: Extension +) : ApplicationCommand(extension) { + override suspend fun call(event: MessageCommandInteractionCreateEvent) { + TODO("Not yet implemented") + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt new file mode 100644 index 0000000000..143c1cb623 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -0,0 +1,28 @@ +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +/** Slash command, executed directly in the chat input. **/ +public abstract class SlashCommand( + extension: Extension +) : ApplicationCommand(extension) { + /** Command description, to explain what your command does. **/ + public lateinit var description: String + + override fun validate() { + super.validate() + + if (::description.isInitialized.not() || description.isEmpty()) { + error("Slash command description must be provided.") + } + } + + override suspend fun runChecks(event: ChatInputCommandInteractionCreateEvent): Boolean = + super.runChecks(event) + + override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { + TODO("Not yet implemented") + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt new file mode 100644 index 0000000000..0b2477d3c6 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt @@ -0,0 +1,6 @@ +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext + +/** Slash command context, containing everything you need for your slash command's execution. **/ +public class SlashCommandContext : ApplicationCommandContext() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt new file mode 100644 index 0000000000..141aff0f73 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -0,0 +1,14 @@ +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** User context command, for right-click actions on users. **/ +public class UserCommand( + extension: Extension +) : ApplicationCommand(extension) { + override suspend fun call(event: UserCommandInteractionCreateEvent) { + TODO("Not yet implemented") + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index f44a6442be..0db4cde46d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.EMPTY_VALUE_STRING import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.parser.StringParser +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user @@ -27,8 +28,6 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.message.MessageCreateEvent -import io.sentry.Sentry -import io.sentry.protocol.SentryId import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -395,36 +394,30 @@ public open class ChatCommand( context.populate() - val firstBreadcrumb = if (sentry.enabled) { - val channel = event.message.getChannelOrNull() - val guild = event.message.getGuildOrNull() + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "command.chat" + message = "Command \"$name\" called." - val data = mutableMapOf( - "arguments" to argString, - "message" to event.message.content - ) + val channel = event.message.getChannelOrNull() + val guild = event.message.getGuildOrNull() + + data["arguments"] = argString + data["message"] = event.message.content - if (channel != null) { - data["channel"] = when (channel) { - is DmChannel -> "Private Message (${channel.id.asString})" - is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" - else -> channel.id.asString + else -> channel.id.asString + } } - } - if (guild != null) { - data["guild"] = "${guild.name} (${guild.id.asString})" + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } } - - sentry.createBreadcrumb( - category = "command", - type = "user", - message = "Command \"$name\" called.", - data = data - ) - } else { - null } @Suppress("TooGenericExceptionCaught") // Anything could happen here @@ -460,7 +453,6 @@ public open class ChatCommand( if (sentry.enabled) { logger.debug { "Submitting error to sentry." } - lateinit var sentryId: SentryId val channel = event.message.getChannelOrNull() val translatedName = when (this) { @@ -470,31 +462,25 @@ public open class ChatCommand( else -> this.getTranslatedName(context.getLocale()) } - Sentry.withScope { + val sentryId = context.sentry.captureException(t, "MessageCommand execution failed.") { val author = event.message.author if (author != null) { - it.user(author) + user(author) } - it.tag("private", "false") + tag("private", "false") if (channel is DmChannel) { - it.tag("private", "true") + tag("private", "true") } - it.tag("command", translatedName) - it.tag("extension", extension.name) - - it.addBreadcrumb(firstBreadcrumb!!) - - context.breadcrumbs.forEach { breadcrumb -> it.addBreadcrumb(breadcrumb) } - - sentryId = Sentry.captureException(t, "MessageCommand execution failed.") - - logger.debug { "Error submitted to Sentry: $sentryId" } + tag("command", translatedName) + tag("extension", extension.name) } + logger.debug { "Error submitted to Sentry: $sentryId" } + sentry.addEventId(sentryId) logger.error(t) { "Error during execution of $name command ($event)" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt index 9a506ee578..fcda12a897 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt @@ -15,8 +15,8 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.rest.builder.interaction.NumberChoiceBuilder import dev.kord.rest.builder.interaction.OptionsBuilder -import dev.kord.rest.builder.interaction.StringChoiceBuilder /** * Argument converter for decimal arguments, converting them into [Double]. @@ -50,5 +50,5 @@ public class DecimalConverter( } override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = - StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + NumberChoiceBuilder(arg.displayName, arg.description).apply { required = true } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt index dc28245caa..f4569722e0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.parser.SlashCommandParser import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user @@ -38,7 +39,6 @@ import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import io.sentry.Sentry -import io.sentry.protocol.SentryId import kotlinx.coroutines.flow.toList import mu.KLogger import mu.KotlinLogging @@ -610,39 +610,33 @@ public open class SlashCommand( context.populate() - val firstBreadcrumb = if (sentry.enabled) { - val channel = context.channel.asChannelOrNull() - val guild = context.guild?.asGuildOrNull() + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "command.slash" + message = "Slash command \"${commandObj.name}\" called." - val data = mutableMapOf( - "command" to commandObj.name - ) + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() - if (this.guild != null) { - data["command.guild"] to this.guild!!.asString - } + data["command"] = commandObj.name - if (channel != null) { - data["channel"] = when (channel) { - is DmChannel -> "Private Message (${channel.id.asString})" - is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + if (this@SlashCommand.guild != null) { + data["command.guild"] to this@SlashCommand.guild!!.asString + } - else -> channel.id.asString + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } } - } - if (guild != null) { - data["guild"] = "${guild.name} (${guild.id.asString})" + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } } - - sentry.createBreadcrumb( - category = "command.slash", - type = "user", - message = "Slash command \"${commandObj.name}\" called.", - data = data - ) - } else { - null } @Suppress("TooGenericExceptionCaught") @@ -678,35 +672,27 @@ public open class SlashCommand( if (sentry.enabled) { logger.debug { "Submitting error to sentry." } - lateinit var sentryId: SentryId - val channel = context.channel val author = context.user.asUserOrNull() - Sentry.withScope { + val sentryId = context.sentry.captureException(t, "Slash command execution failed.") { if (author != null) { - it.user(author) + user(author) } - it.tag("private", "false") + tag("private", "false") if (channel is DmChannel) { - it.tag("private", "true") + tag("private", "true") } - it.tag("command", commandObj.name) - it.tag("extension", commandObj.extension.name) - - it.addBreadcrumb(firstBreadcrumb!!) - - context.breadcrumbs.forEach { breadcrumb -> it.addBreadcrumb(breadcrumb) } - - sentryId = Sentry.captureException(t, "SlashCommand execution failed.") + tag("command", commandObj.name) + tag("extension", commandObj.extension.name) - logger.debug { "Error submitted to Sentry: $sentryId" } + Sentry.captureException(t, "SlashCommand execution failed.") } - sentry.addEventId(sentryId) + logger.debug { "Error submitted to Sentry: $sentryId" } logger.error(t) { "Error during execution of ${commandObj.name} slash command ($event)" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt index 499a28e96a..9808d53545 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt @@ -12,6 +12,8 @@ import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.components.contexts.ActionableComponentContext import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.SentryContext import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user import com.kotlindiscord.kord.extensions.utils.ackEphemeral @@ -25,11 +27,8 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.interaction.ComponentInteraction import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import io.sentry.Sentry -import io.sentry.protocol.SentryId import mu.KotlinLogging import org.koin.core.component.inject -import java.io.Serializable import java.util.* /** @@ -164,33 +163,30 @@ public abstract class ActionableComponentBuilder() + if (sentry.enabled) { + sentryContext.breadcrumb(BreadcrumbType.User) { + category = "interaction" + type = "user" + message = "Interaction for component \"$id\" received." - if (channel != null) { - data["channel"] = when (channel) { - is DmChannel -> "Private Message (${channel.id.asString})" - is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + val channel = channelFor(event) + val guild = guildFor(event)?.asGuildOrNull() - else -> channel.id.asString + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } } - } - if (guild != null) { - data["guild"] = "${guild.name} (${guild.id.asString})" + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } } - - sentry.createBreadcrumb( - category = "interaction", - type = "user", - message = "Interaction for component \"$id\" received.", - data = data - ) - } else { - null } val interaction = event.interaction as T @@ -212,7 +208,7 @@ public abstract class ActionableComponentBuilder( public open val event: ComponentInteractionCreateEvent, public open val components: Components, public open var interactionResponse: InteractionResponseBehavior? = null, - public open val interaction: T = event.interaction as T + public open val interaction: T = event.interaction as T, + public open val sentry: SentryContext ) : KoinComponent { /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() - /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() - - /** A list of Sentry breadcrumbs created during interaction execution. **/ - public open val breadcrumbs: MutableList = mutableListOf() - /** Cached locale variable, stored and retrieved by [getLocale]. **/ public open var resolvedLocale: Locale? = null @@ -166,27 +160,6 @@ public abstract class ActionableComponentContext( return (interactionResponse as PublicInteractionResponseBehavior).followUp(builder) } - /** - * Add a Sentry breadcrumb to this context. - * - * This should be used for the purposes of tracing what exactly is happening during your - * interaction processing. If the bot administrator decides to enable Sentry integration, the - * breadcrumbs will be sent to Sentry when there's an interaction processing error. - */ - public fun breadcrumb( - category: String? = null, - level: SentryLevel? = null, - type: String? = null, - - data: Map = mapOf() - ): Breadcrumb { - val crumb = sentry.createBreadcrumb(category, level, null, type, data) - - breadcrumbs.add(crumb) - - return crumb - } - /** Resolve the locale for this context. **/ public suspend fun getLocale(): Locale { var locale: Locale? = resolvedLocale diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt index 92d5c6cc7d..abfa4636ac 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.contexts import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.sentry.SentryContext import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.* @@ -19,7 +20,8 @@ public open class InteractiveButtonContext( event: ComponentInteractionCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, - interaction: ButtonInteraction = event.interaction as ButtonInteraction + interaction: ButtonInteraction = event.interaction as ButtonInteraction, + sentryContext: SentryContext ) : KoinComponent, ActionableComponentContext( - extension, event, components, interactionResponse, interaction + extension, event, components, interactionResponse, interaction, sentryContext ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt index 3caab36151..0799754d2f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.contexts import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.sentry.SentryContext import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.SelectMenuInteraction @@ -20,9 +21,10 @@ public open class MenuContext( event: ComponentInteractionCreateEvent, components: Components, interactionResponse: InteractionResponseBehavior? = null, - interaction: SelectMenuInteraction = event.interaction as SelectMenuInteraction + interaction: SelectMenuInteraction = event.interaction as SelectMenuInteraction, + sentryContext: SentryContext ) : KoinComponent, ActionableComponentContext( - extension, event, components, interactionResponse, interaction + extension, event, components, interactionResponse, interaction, sentryContext ) { /** Quick access to the selected values. **/ public val selected: List = interaction.values diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventContext.kt index 57ca5cc55e..7a1d7e2c65 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventContext.kt @@ -4,10 +4,8 @@ import com.kotlindiscord.kord.extensions.checks.channelFor import com.kotlindiscord.kord.extensions.checks.guildFor import com.kotlindiscord.kord.extensions.checks.userFor import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import com.kotlindiscord.kord.extensions.sentry.SentryContext import dev.kord.core.event.Event -import io.sentry.Breadcrumb -import io.sentry.SentryLevel import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* @@ -27,11 +25,8 @@ public open class EventContext( /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() - /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() - - /** A list of Sentry breadcrumbs created during event processing. **/ - public open val breadcrumbs: MutableList = mutableListOf() + /** Current Sentry context, containing breadcrumbs and other goodies. **/ + public val sentry: SentryContext = SentryContext() /** * Given a translation key and optional bundle name, return the translation for the locale provided by the bot's @@ -73,26 +68,4 @@ public open class EventContext( key: String, replacements: Array = arrayOf() ): String = translate(key, eventHandler.extension.bundle, replacements) - - /** - * Add a Sentry breadcrumb to this event context. - * - * This should be used for the purposes of tracing what exactly is happening during your - * event processing. If the bot administrator decides to enable Sentry integration, the - * breadcrumbs will be sent to Sentry when there's an event processing error. - */ - public fun breadcrumb( - category: String? = null, - level: SentryLevel? = null, - message: String? = null, - type: String? = null, - - data: Map = mapOf() - ): Breadcrumb { - val crumb = sentry.createBreadcrumb(category, level, message, type, data) - - breadcrumbs.add(crumb) - - return crumb - } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt index 3169c01fba..5914148285 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt @@ -7,20 +7,18 @@ import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.utils.getKoin -import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.Event -import io.sentry.Sentry import kotlinx.coroutines.Job import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.io.Serializable import java.util.* private val logger = KotlinLogging.logger {} @@ -175,17 +173,17 @@ public open class EventHandler( val context = EventContext(this, event) val eventName = event::class.simpleName - val firstBreadcrumb = if (sentry.enabled) { - val data = mutableMapOf() + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.Info) { + category = "event" + message = "Event \"$eventName\" fired." - val channelId = channelIdFor(event) - val guildBehavior = guildFor(event) - val messageBehavior = messageFor(event) - val roleBehavior = roleFor(event) - val userBehavior = userFor(event) - - if (channelId != null) { - val channel = kord.getChannel(Snowflake(channelId)) + val channel = topChannelFor(event) + val guildBehavior = guildFor(event) + val messageBehavior = messageFor(event) + val roleBehavior = roleFor(event) + val thread = threadFor(event)?.asChannel() + val userBehavior = userFor(event) if (channel != null) { data["channel"] = when (channel) { @@ -194,53 +192,46 @@ public open class EventHandler( else -> channel.id.asString } - } else { - data["channel"] = channelId } - } - if (guildBehavior != null) { - val guild = guildBehavior.asGuildOrNull() + if (thread != null) { + data["thread"] = "#${thread.name} (${thread.id.asString})" + } - data["guild"] = if (guild != null) { - "${guild.name} (${guild.id.asString})" - } else { - guildBehavior.id.asString + if (guildBehavior != null) { + val guild = guildBehavior.asGuildOrNull() + + data["guild"] = if (guild != null) { + "${guild.name} (${guild.id.asString})" + } else { + guildBehavior.id.asString + } } - } - if (messageBehavior != null) { - data["message"] = messageBehavior.id.asString - } + if (messageBehavior != null) { + data["message"] = messageBehavior.id.asString + } - if (roleBehavior != null) { - val role = roleBehavior.guild.getRoleOrNull(roleBehavior.id) + if (roleBehavior != null) { + val role = roleBehavior.guild.getRoleOrNull(roleBehavior.id) - data["role"] = if (role != null) { - "@${role.name} (${role.id.asString})" - } else { - roleBehavior.id.asString + data["role"] = if (role != null) { + "@${role.name} (${role.id.asString})" + } else { + roleBehavior.id.asString + } } - } - if (userBehavior != null) { - val user = userBehavior.asUserOrNull() + if (userBehavior != null) { + val user = userBehavior.asUserOrNull() - data["user"] = if (user != null) { - "${user.tag} (${user.id.asString})" - } else { - userBehavior.id.asString + data["user"] = if (user != null) { + "${user.tag} (${user.id.asString})" + } else { + userBehavior.id.asString + } } } - - sentry.createBreadcrumb( - category = "event", - type = "info", - message = "Event \"$eventName\" fired.", - data = data - ) - } else { - null } @Suppress("TooGenericExceptionCaught") // Anything could happen here @@ -250,19 +241,13 @@ public open class EventHandler( if (sentry.enabled && extension.bot.extensions.containsKey("sentry")) { logger.debug { "Submitting error to sentry." } - Sentry.withScope { - it.tag("event", eventName ?: "Unknown") - it.tag("extension", extension.name) - - it.addBreadcrumb(firstBreadcrumb!!) - - context.breadcrumbs.forEach { breadcrumb -> it.addBreadcrumb(breadcrumb) } - - val sentryId = Sentry.captureException(t, "Event processing failed.") - - logger.debug { "Error submitted to Sentry: $sentryId" } + val sentryId = context.sentry.captureException(t, "Event processing failed.") { + tag("event", eventName ?: "Unknown") + tag("extension", extension.name) } + logger.debug { "Error submitted to Sentry: $sentryId" } + logger.error(t) { "Error during execution of event handler ($eventName)" } } else { logger.error(t) { "Error during execution of event handler ($eventName)" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index 91fb2f6b54..ea8c9cb840 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -28,7 +28,7 @@ public class SentryExtension : Extension() { override val name: String = "sentry" /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() + public val sentryAdapter: SentryAdapter by inject() /** Bot settings. **/ public val botSettings: ExtensibleBotBuilder by inject() @@ -39,13 +39,13 @@ public class SentryExtension : Extension() { @Suppress("StringLiteralDuplication") // It's the command name override suspend fun setup() { - if (sentry.enabled) { + if (sentryAdapter.enabled) { slashCommand(::FeedbackSlashArgs) { name = "extensions.sentry.commandName" description = "extensions.sentry.commandDescription.short" action { - if (!sentry.hasEventId(arguments.id)) { + if (!sentryAdapter.hasEventId(arguments.id)) { ephemeralFollowUp { content = translate("extensions.sentry.error.invalidId") } @@ -61,7 +61,7 @@ public class SentryExtension : Extension() { ) Sentry.captureUserFeedback(feedback) - sentry.removeEventId(arguments.id) + sentryAdapter.removeEventId(arguments.id) ephemeralFollowUp { content = translate("extensions.sentry.thanks") @@ -76,7 +76,7 @@ public class SentryExtension : Extension() { aliasKey = "extensions.sentry.commandAliases" action { - if (!sentry.hasEventId(arguments.id)) { + if (!sentryAdapter.hasEventId(arguments.id)) { message.respond( translate("extensions.sentry.error.invalidId"), pingInReply = settings.pingInReply @@ -94,7 +94,7 @@ public class SentryExtension : Extension() { ) Sentry.captureUserFeedback(feedback) - sentry.removeEventId(arguments.id) + sentryAdapter.removeEventId(arguments.id) message.respond( translate("extensions.sentry.thanks") diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/BreadcrumbType.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/BreadcrumbType.kt new file mode 100644 index 0000000000..339766fda2 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/BreadcrumbType.kt @@ -0,0 +1,39 @@ +package com.kotlindiscord.kord.extensions.sentry + +/** + * Sealed class representing all the types of breadcrumbs that Sentry supports. + * + * @param name The breadcrumb type name, sent to Sentry + * @param requiredKeys Array of required keys that must be present in the breadcrumb data for it to be valid, if any + */ +public sealed class BreadcrumbType(public val name: String, public vararg val requiredKeys: String) { + /** Typically a debug log message. **/ + public object Debug : BreadcrumbType("debug") + + /** The default breadcrumb type. **/ + public object Default : BreadcrumbType("default") + + /** A detected or unhandled error. **/ + public object Error : BreadcrumbType("error") + + /** A HTTP request sent by your bot. **/ + public object HTTP : BreadcrumbType("http") + + /** Information on what's been going on. **/ + public object Info : BreadcrumbType("info") + + /** Navigation action, requiring from/to data keys. **/ + public object Navigation : BreadcrumbType("navigation", "from", "to") + + /** A query made by a user. **/ + public object Query : BreadcrumbType("query") + + /** A tracing event. **/ + public object Transaction : BreadcrumbType("transaction") + + /** A UI interaction. **/ + public object UI : BreadcrumbType("ui") + + /** A user interaction. **/ + public object User : BreadcrumbType("user") +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt index 906804f4d3..2ca8de2dd7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt @@ -12,6 +12,8 @@ import com.kotlindiscord.kord.extensions.commands.parser.Arguments import dev.kord.common.annotation.KordPreview import io.sentry.protocol.SentryId +// TODO: Move to annotation + /** * Create a Sentry ID argument converter, for single arguments. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryAdapter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryAdapter.kt index 87584f5e02..afeccab974 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryAdapter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryAdapter.kt @@ -101,9 +101,12 @@ public open class SentryAdapter { */ public fun sendFeedback( id: SentryId, + comments: String? = null, email: String? = null, - name: String? = null + name: String? = null, + + removeId: Boolean = true ) { if (!enabled) error("Sentry integration has not yet been configured.") @@ -114,29 +117,10 @@ public open class SentryAdapter { if (name != null) feedback.name = name Sentry.captureUserFeedback(feedback) - } - - /** - * Convenience function for creating a Breadcrumb object. - */ - public fun createBreadcrumb( - category: String? = null, - level: SentryLevel? = null, - message: String? = null, - type: String? = null, - - data: Map = mapOf() - ): Breadcrumb { - val breadcrumbObj = Breadcrumb() - - if (category != null) breadcrumbObj.category = category - if (level != null) breadcrumbObj.level = level - if (message != null) breadcrumbObj.message = message - if (type != null) breadcrumbObj.type = type - data.toSortedMap().forEach { (key, value) -> breadcrumbObj.setData(key, value) } - - return breadcrumbObj + if (removeId) { + removeEventId(id) + } } /** Register an event ID that a user may provide feedback for. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt new file mode 100644 index 0000000000..3682b7b35a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryContext.kt @@ -0,0 +1,208 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.sentry + +import io.sentry.* +import io.sentry.protocol.SentryId +import mu.KLogger +import mu.KotlinLogging +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** + * Context object for keeping track of Sentry breadcrumbs and providing convenient APIs for submitting them, along with + * transaction functions. + * + * Generally speaking, you'll probably want to use this instead of touching Sentry (or the adapter) directly. + */ +public class SentryContext : KoinComponent { + /** Quick access to the Sentry adapter, if required. **/ + public val adapter: SentryAdapter by inject() + + /** + * List of Sentry breadcrumbs referred to as part of this context, You likely won't need to touch this directly, + * but it's available for more advanced use-cases. + */ + public val breadcrumbs: MutableList = mutableListOf() + + /** Create a transaction with the given name and operation, and use it to measure the given callable. **/ + public inline fun transaction(name: String, operation: String, body: (ITransaction).() -> Unit) { + val transaction = Sentry.startTransaction(name, operation) + + transaction(transaction, body) + } + + /** Use the given transaction to measure the given callable. **/ + public inline fun transaction(transaction: ITransaction, body: (ITransaction).() -> Unit) { + try { + body(transaction) + } catch (t: Throwable) { + transaction.throwable = t + transaction.status = SpanStatus.INTERNAL_ERROR + } finally { + transaction.finish() + } + } + + /** Register a breadcrumb of the given [type], using the [builder] to modify it and add context. **/ + public inline fun breadcrumb(type: BreadcrumbType = BreadcrumbType.Default, builder: Breadcrumb.() -> Unit) { + val breadcrumb = Breadcrumb() + breadcrumb.type = type.name + + builder(breadcrumb) + + if (!type.requiredKeys.all { breadcrumb.data.containsKey(it) }) { + val logger: KLogger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.sentry.SentryContext") + + logger.warn { + "Ignoring provided breadcrumb type \"${type.name}\" - the following data keys are required: " + + type.requiredKeys.joinToString() + } + + breadcrumb.type = BreadcrumbType.Default.name + } + + breadcrumbs.add(breadcrumb) + } + + /** Register a [breadcrumb] object that's already been created. **/ + public fun breadcrumb(breadcrumb: Breadcrumb): Boolean = + breadcrumbs.add(breadcrumb) + + /** Capture a [SentryEvent], submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureEvent( + event: SentryEvent, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureEvent(event) + } + + adapter.addEventId(id) + + return id + } + + /** Capture a [SentryEvent], submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureEvent( + event: SentryEvent, + hint: Any?, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureEvent(event, hint) + } + + adapter.addEventId(id) + + return id + } + + /** Capture a [Throwable] exception, submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureException( + t: Throwable, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureException(t) + } + + adapter.addEventId(id) + + return id + } + + /** Capture a [Throwable] exception, submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureException( + t: Throwable, + hint: Any?, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureException(t, hint) + } + + adapter.addEventId(id) + + return id + } + + /** Capture a [UserFeedback] object, submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureFeedback( + feedback: UserFeedback, + crossinline body: (Scope).() -> Unit + ) { + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + Sentry.captureUserFeedback(feedback) + } + } + + /** Capture a [message] String, submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureMessage( + message: String, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureMessage(message) + } + + adapter.addEventId(id) + + return id + } + + /** Capture a [message] String, submitting it to Sentry with the breadcrumbs in this context. **/ + public inline fun captureMessage( + message: String, + level: SentryLevel, + crossinline body: (Scope).() -> Unit + ): SentryId { + lateinit var id: SentryId + + Sentry.withScope { + body(it) + + breadcrumbs.forEach(it::addBreadcrumb) + + id = Sentry.captureMessage(message, level) + } + + adapter.addEventId(id) + + return id + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Utils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/_Utils.kt similarity index 69% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Utils.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/_Utils.kt index bec6d7e095..a7b2054b8b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Utils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/_Utils.kt @@ -1,8 +1,9 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.sentry -import io.sentry.Breadcrumb -import io.sentry.Scope -import io.sentry.SentryLevel +import io.sentry.* +import io.sentry.Sentry.startTransaction import io.sentry.protocol.User /** @@ -63,3 +64,22 @@ public fun Scope.breadcrumb( this.addBreadcrumb(breadcrumbObj, hint) } + +/** Convenience function for creating and testing a sub-transaction. **/ +public inline fun ITransaction.transaction(name: String, operation: String, body: (ITransaction).() -> T) { + val transaction = startTransaction(name, operation) + + transaction(transaction, body) +} + +/** Convenience function for testing a sub-transaction. **/ +public inline fun ITransaction.transaction(transaction: ITransaction, body: (ITransaction).() -> T) { + try { + body(transaction) + } catch (t: Throwable) { + transaction.throwable = t + transaction.status = SpanStatus.INTERNAL_ERROR + } finally { + transaction.finish() + } +} From fe4a20f7a55f97f7b8ce21a532f198df08dd3a23 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 24 Aug 2021 18:20:00 +0100 Subject: [PATCH 013/131] Further application command layout work --- .../application/ApplicationCommand.kt | 1 + .../application/ApplicationCommandContext.kt | 128 +++++++++++++++++- .../EphemeralApplicationCommandContext.kt | 21 +++ .../PublicApplicationCommandContext.kt | 17 +++ .../message/EphemeralMessageCommand.kt | 8 ++ .../message/EphemeralMessageCommandContext.kt | 12 ++ .../application/message/MessageCommand.kt | 10 +- .../message/MessageCommandContext.kt | 19 +++ .../message/PublicMessageCommand.kt | 8 ++ .../message/PublicMessageCommandContext.kt | 12 ++ .../application/slash/SlashCommandContext.kt | 7 +- .../commands/application/user/UserCommand.kt | 2 +- .../application/user/UserCommandContext.kt | 15 ++ 13 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 5fb185f570..f93f1aa9cf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -193,6 +193,7 @@ public abstract class ApplicationCommand( /** Called in order to execute the command. **/ public open suspend fun doCall(event: E) { + call(event) } /** Runs standard checks that can be handled in a generic way, without worrying about subclass-specific checks. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt index fa36e84d5d..8046de5e40 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -1,4 +1,128 @@ package com.kotlindiscord.kord.extensions.commands.application -/** Base class representing the shared functionality for an application command's context. **/ -public abstract class ApplicationCommandContext +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.checks.channelFor +import com.kotlindiscord.kord.extensions.checks.guildFor +import com.kotlindiscord.kord.extensions.checks.userFor +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.SentryContext +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.MemberBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.util.* + +/** + * Base class representing the shared functionality for an application command's context. + * + * @param genericEvent Generic event object to populate data from. + * @param genericCommand Generic command object that this context belongs to. + */ +public abstract class ApplicationCommandContext( + public val genericEvent: ApplicationInteractionCreateEvent, + public val genericCommand: ApplicationCommand<*> +) : KoinComponent { + /** Current bot setting object. **/ + public val botSettings: ExtensibleBotBuilder by inject() + + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + + /** Current Sentry context, containing breadcrumbs and other goodies. **/ + public val sentry: SentryContext = SentryContext() + + /** Cached locale variable, stored and retrieved by [getLocale]. **/ + public open var resolvedLocale: Locale? = null + + /** Channel this command was executed within. **/ + public open lateinit var channel: MessageChannelBehavior + + /** Guild this command was executed within, if any. **/ + public open var guild: GuildBehavior? = null + + /** Member that executed this command, if on a guild. **/ + public open var member: MemberBehavior? = null + + /** User that executed this command. **/ + public open lateinit var user: UserBehavior + + /** Called before processing, used to populate any extra variables from event data. **/ + public open suspend fun populate() { + // NOTE: This must always be alphabetical, some latter calls rely on earlier ones + + channel = getChannel() + guild = getGuild() + member = getMember() + user = getUser() + } + + /** Extract channel information from event data, if that context is available. **/ + public open suspend fun getChannel(): MessageChannelBehavior = + genericEvent.interaction.getChannel() + + /** Extract guild information from event data, if that context is available. **/ + public open suspend fun getGuild(): GuildBehavior? = + (channel as? GuildMessageChannel)?.guild + + /** Extract member information from event data, if that context is available. **/ + public open suspend fun getMember(): MemberBehavior? = + guild?.getMember(genericEvent.interaction.user.id) + + /** Extract user information from event data, if that context is available. **/ + public open suspend fun getUser(): UserBehavior = + genericEvent.interaction.user + + /** Resolve the locale for this command context. **/ + public suspend fun getLocale(): Locale { + var locale: Locale? = resolvedLocale + + if (locale != null) { + return locale + } + + val guild = guildFor(genericEvent) + val channel = channelFor(genericEvent) + val user = userFor(genericEvent) + + for (resolver in botSettings.i18nBuilder.localeResolvers) { + val result = resolver(guild, channel, user) + + if (result != null) { + locale = result + break + } + } + + resolvedLocale = locale ?: botSettings.i18nBuilder.defaultLocale + + return resolvedLocale!! + } + + /** + * Given a translation key and bundle name, return the translation for the locale provided by the bot's configured + * locale resolvers. + */ + public suspend fun translate( + key: String, + bundleName: String?, + replacements: Array = arrayOf() + ): String { + val locale = getLocale() + + return translationsProvider.translate(key, locale, bundleName, replacements) + } + + /** + * Given a translation key and possible replacements,return the translation for the given locale in the + * extension's configured bundle, for the locale provided by the bot's configured locale resolvers. + */ + public suspend fun translate(key: String, replacements: Array = arrayOf()): String = translate( + key, + genericCommand.extension.bundle, + replacements + ) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt new file mode 100644 index 0000000000..0092ea676b --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt @@ -0,0 +1,21 @@ +package com.kotlindiscord.kord.extensions.commands.application + +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.followUpEphemeral +import dev.kord.core.entity.interaction.EphemeralFollowupMessage +import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder + +/** Interface representing an ephemeral-only application command context. **/ +public interface EphemeralApplicationCommandContext { + /** Response created by acknowledging the interaction ephemerally. **/ + public val interactionResponse: EphemeralInteractionResponseBehavior +} + +/** + * Respond to the current interaction with an ephemeral followup. + * + * **Note:** Calling this twice will result in a public followup! + */ +public suspend inline fun EphemeralApplicationCommandContext.respond( + builder: EphemeralFollowupMessageCreateBuilder.() -> Unit +): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt new file mode 100644 index 0000000000..3a8570bb7e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt @@ -0,0 +1,17 @@ +package com.kotlindiscord.kord.extensions.commands.application + +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.entity.interaction.PublicFollowupMessage +import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder + +/** Interface representing a public-only application command context. **/ +public interface PublicApplicationCommandContext { + /** Response created by acknowledging the interaction publicly. **/ + public val interactionResponse: PublicInteractionResponseBehavior +} + +/** Respond to the current interaction with a public followup. **/ +public suspend inline fun PublicApplicationCommandContext.respond( + builder: PublicFollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage = interactionResponse.followUp(builder) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt new file mode 100644 index 0000000000..da971b65ba --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -0,0 +1,8 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.extensions.Extension + +/** Ephemeral-followup-only message command. **/ +public class EphemeralMessageCommand( + extension: Extension +) : MessageCommand(extension) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt new file mode 100644 index 0000000000..66e5ca208f --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** Ephemeral-only message command context. **/ +public class EphemeralMessageCommandContext( + override val event: MessageCommandInteractionCreateEvent, + override val command: MessageCommand, + override val interactionResponse: EphemeralInteractionResponseBehavior +) : MessageCommandContext(event, command), EphemeralApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 477b32df88..985e662938 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -5,9 +5,17 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent /** Message context command, for right-click actions on messages. **/ -public class MessageCommand( +public open class MessageCommand( extension: Extension ) : ApplicationCommand(extension) { + /** Command body, to be called when the command is executed. **/ + public lateinit var body: suspend C.() -> Unit + + /** Call this to supply a command [body], to be called when the command is executed. **/ + public fun action(action: suspend C.() -> Unit) { + body = action + } + override suspend fun call(event: MessageCommandInteractionCreateEvent) { TODO("Not yet implemented") } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt new file mode 100644 index 0000000000..e1d633744d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt @@ -0,0 +1,19 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext +import dev.kord.core.entity.Message +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** + * Message command context, containing everything you need for your message command's execution. + * + * @param event Event that triggered this message command. + * @param command Message command instance. + */ +public open class MessageCommandContext( + public open val event: MessageCommandInteractionCreateEvent, + public open val command: MessageCommand, +) : ApplicationCommandContext(event, command) { + /** Messages that this message command is being executed against. **/ + public val targetMessages: Collection = event.interaction.messages?.values ?: listOf() +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt new file mode 100644 index 0000000000..67206d191a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -0,0 +1,8 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.extensions.Extension + +/** Public-followup-only message command. **/ +public class PublicMessageCommand( + extension: Extension +) : MessageCommand(extension) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt new file mode 100644 index 0000000000..0e06a5be35 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.commands.application.message + +import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** Public-only message command context. **/ +public class PublicMessageCommandContext( + override val event: MessageCommandInteractionCreateEvent, + override val command: MessageCommand, + override val interactionResponse: PublicInteractionResponseBehavior +) : MessageCommandContext(event, command), PublicApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt index 0b2477d3c6..230b295c2a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt @@ -1,6 +1,11 @@ package com.kotlindiscord.kord.extensions.commands.application.slash +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext +import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent /** Slash command context, containing everything you need for your slash command's execution. **/ -public class SlashCommandContext : ApplicationCommandContext() +public class SlashCommandContext( + genericEvent: ApplicationInteractionCreateEvent, + genericCommand: ApplicationCommand<*> +) : ApplicationCommandContext(genericEvent, genericCommand) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 141aff0f73..c7c45588f9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -5,7 +5,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent /** User context command, for right-click actions on users. **/ -public class UserCommand( +public abstract class UserCommand( extension: Extension ) : ApplicationCommand(extension) { override suspend fun call(event: UserCommandInteractionCreateEvent) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt new file mode 100644 index 0000000000..47d920985a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt @@ -0,0 +1,15 @@ +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** + * User command context, containing everything you need for your user command's execution. + * + * @param event Event that triggered this message command. + * @param command Message command instance. + */ +public class UserCommandContext( + event: UserCommandInteractionCreateEvent, + command: UserCommand +) : ApplicationCommandContext(event, command) From febdb568584de50298dd0aef55dc4de7530242ef Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Fri, 27 Aug 2021 13:17:00 +0100 Subject: [PATCH 014/131] Continued structural work, Gradle signing --- .github/workflows/publish.yml | 9 ++++ .github/workflows/tag.yml | 11 ++++- annotation-processor/build.gradle.kts | 6 +++ annotations/build.gradle.kts | 6 +++ extra-modules/extra-common/build.gradle.kts | 6 +++ extra-modules/extra-mappings/build.gradle.kts | 6 +++ .../kord/extensions/test/bot/Bot.kt | 2 +- kord-extensions/build.gradle.kts | 6 +++ .../kord/extensions/ExtensibleBot.kt | 4 +- .../builders/ExtensibleBotBuilder.kt | 48 +++++++++---------- .../kord/extensions/checks/types/_Types.kt | 17 +++++++ .../application/ApplicationCommand.kt | 4 ++ .../application/ApplicationCommandRegistry.kt | 17 ++++++- .../application/slash/SlashCommand.kt | 2 +- .../commands/application/user/UserCommand.kt | 2 +- .../extensions/commands/slash/SlashCommand.kt | 4 +- .../commands/slash/SlashCommandRegistry.kt | 4 +- .../kord/extensions/test/bot/Bot.kt | 2 +- modules/java-time/build.gradle.kts | 6 +++ modules/time4j/build.gradle.kts | 6 +++ token-parser/build.gradle.kts | 6 +++ 21 files changed, 137 insertions(+), 37 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 393e8294d2..6fb652cf33 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,11 +18,20 @@ jobs: with: java-version: 1.14 + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v3 + + with: + gpg-private-key: ${{ secrets.GPG_KEY }} + passphrase: ${{ secrets.GPG_PASSWORD }} + - name: Set up Gradle properties run: | mkdir -p ~/.gradle echo "githubToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.gradle/gradle.properties echo -e "\norg.gradle.jvmargs=-XX:MaxMetaspaceSize=5G" >> ~/.gradle/gradle.properties + echo -e "\nsigning.gnupg.keyName=BFAAD5D6093EF5E62BC9A16A10DB8C6B4AE61C2F" >> ~/.gradle/gradle.properties + echo -e "\nsigning.gnupg.passphrase=${{ secrets.GPG_PASSWORD }}" >> ~/.gradle/gradle.properties - name: Gradle (Build) run: sh gradlew build diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 0964254988..695382c896 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -16,7 +16,14 @@ jobs: uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: 1.14 + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v3 + + with: + gpg-private-key: ${{ secrets.GPG_KEY }} + passphrase: ${{ secrets.GPG_PASSWORD }} - name: Set up Kotlin uses: fwilhe2/setup-kotlin@main @@ -25,6 +32,8 @@ jobs: run: | mkdir -p ~/.gradle echo "org.gradle.jvmargs=-XX:MaxMetaspaceSize=5G" >> ~/.gradle/gradle.properties + echo -e "\nsigning.gnupg.keyName=BFAAD5D6093EF5E62BC9A16A10DB8C6B4AE61C2F" >> ~/.gradle/gradle.properties + echo -e "\nsigning.gnupg.passphrase=${{ secrets.GPG_PASSWORD }}" >> ~/.gradle/gradle.properties - name: Set up git credentials uses: oleksiyrudenko/gha-git-credentials@v2-latest diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts index d9c6f0a929..00c2a70d09 100644 --- a/annotation-processor/build.gradle.kts +++ b/annotation-processor/build.gradle.kts @@ -3,6 +3,7 @@ import java.net.URL plugins { `maven-publish` + signing kotlin("jvm") @@ -81,6 +82,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts index 6f6205391c..aa4063323d 100644 --- a/annotations/build.gradle.kts +++ b/annotations/build.gradle.kts @@ -3,6 +3,7 @@ import java.net.URL plugins { `maven-publish` + signing kotlin("jvm") @@ -75,6 +76,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() diff --git a/extra-modules/extra-common/build.gradle.kts b/extra-modules/extra-common/build.gradle.kts index f973315098..92402bcd31 100644 --- a/extra-modules/extra-common/build.gradle.kts +++ b/extra-modules/extra-common/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `maven-publish` + signing id("io.gitlab.arturbosch.detekt") @@ -90,3 +91,8 @@ publishing { } } } + +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} diff --git a/extra-modules/extra-mappings/build.gradle.kts b/extra-modules/extra-mappings/build.gradle.kts index fa90d90bab..13bbbddc82 100644 --- a/extra-modules/extra-mappings/build.gradle.kts +++ b/extra-modules/extra-mappings/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `maven-publish` + signing id("io.gitlab.arturbosch.detekt") @@ -106,3 +107,8 @@ publishing { } } } + +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} diff --git a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 6795038fbe..deeb9c6f8f 100644 --- a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -15,7 +15,7 @@ suspend fun main() { check(isNotbot) } - slashCommands { + applicationCommands { enabled = true } diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts index b44a1a1443..1c39a53feb 100644 --- a/kord-extensions/build.gradle.kts +++ b/kord-extensions/build.gradle.kts @@ -13,6 +13,7 @@ buildscript { plugins { `maven-publish` + signing kotlin("jvm") @@ -122,6 +123,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 50c73b2cc7..af3c7b8ff6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -154,7 +154,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva if (!initialized) { // We do this because a reconnect will cause this event to happen again. initialized = true - if (settings.slashCommandsBuilder.enabled) { + if (settings.applicationCommandsBuilder.enabled) { getKoin().get().syncAll() } else { logger.info { @@ -173,7 +173,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } } - if (settings.slashCommandsBuilder.enabled) { + if (settings.applicationCommandsBuilder.enabled) { on { getKoin().get().handle(this) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d29da60040..5548d8e40b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -85,7 +85,7 @@ public open class ExtensibleBotBuilder { public var shardingBuilder: ((recommended: Int) -> Shards)? = null /** @suppress Builder that shouldn't be set directly by the user. **/ - public val slashCommandsBuilder: SlashCommandsBuilder = SlashCommandsBuilder() + public val applicationCommandsBuilder: ApplicationCommandsBuilder = ApplicationCommandsBuilder() /** @suppress List of Kord builders, shouldn't be set directly by the user. **/ public val kordBuilders: MutableList Unit> = mutableListOf() @@ -139,13 +139,13 @@ public open class ExtensibleBotBuilder { } /** - * DSL function used to configure the bot's slash command options. + * DSL function used to configure the bot's application command options. * - * @see SlashCommandsBuilder + * @see ApplicationCommandsBuilder */ @BotBuilderDSL - public suspend fun slashCommands(builder: suspend SlashCommandsBuilder.() -> Unit) { - builder(slashCommandsBuilder) + public suspend fun applicationCommands(builder: suspend ApplicationCommandsBuilder.() -> Unit) { + builder(applicationCommandsBuilder) } /** @@ -226,7 +226,7 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } loadModule { single { messageCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } - loadModule { single { slashCommandsBuilder.slashRegistryBuilder() } bind SlashCommandRegistry::class } + loadModule { single { applicationCommandsBuilder.slashRegistryBuilder() } bind SlashCommandRegistry::class } loadModule { single { @@ -792,8 +792,8 @@ public open class ExtensibleBotBuilder { /** Prefix to require for command invocations on Discord. Defaults to `"!"`. **/ public var defaultPrefix: String = "!" - /** Whether to register and process message commands. Defaults to `true`. **/ - public var enabled: Boolean = true + /** Whether to register and process message commands. Defaults to `false`. **/ + public var enabled: Boolean = false /** Number of threads to use for command execution. Defaults to twice the number of CPU threads. **/ public var threads: Int = Runtime.getRuntime().availableProcessors() * 2 @@ -855,16 +855,16 @@ public open class ExtensibleBotBuilder { } } - /** Builder used for configuring the bot's slash command options. **/ + /** Builder used for configuring the bot's application command options. **/ @BotBuilderDSL - public class SlashCommandsBuilder { - /** Whether to register and process slash commands. Defaults to `false`. **/ + public class ApplicationCommandsBuilder { + /** Whether to register and process application commands. Defaults to `false`. **/ public var enabled: Boolean = false - /** The guild ID to use for all global slash commands. Intended for testing. **/ + /** The guild ID to use for all global application commands. Intended for testing. **/ public var defaultGuild: Snowflake? = null - /** Whether to attempt to register the bot's slash commands. Intended for multi-instance sharded bots. **/ + /** Whether to attempt to register the bot's application commands. Intended for multi-instance sharded bots. **/ public var register: Boolean = true /** @suppress Builder that shouldn't be set directly by the user. **/ @@ -875,19 +875,19 @@ public open class ExtensibleBotBuilder { * * These checks will be checked against all slash commands. */ - public val checkList: MutableList> = mutableListOf() + public val slashCheckList: MutableList> = mutableListOf() - /** Set a guild ID to use for all global slash commands. Intended for testing. **/ + /** Set a guild ID to use for all global application commands. Intended for testing. **/ public fun defaultGuild(id: Snowflake) { defaultGuild = id } - /** Set a guild ID to use for all global slash commands. Intended for testing. **/ + /** Set a guild ID to use for all global application commands. Intended for testing. **/ public fun defaultGuild(id: Long) { defaultGuild = Snowflake(id) } - /** Set a guild ID to use for all global slash commands. Intended for testing. **/ + /** Set a guild ID to use for all global application commands. Intended for testing. **/ public fun defaultGuild(id: String) { defaultGuild = Snowflake(id) } @@ -901,10 +901,10 @@ public open class ExtensibleBotBuilder { } /** - * Define a check which must pass for a command to be executed. This check will be applied to all + * Define a check which must pass for a slash command to be executed. This check will be applied to all * slash commands. * - * A command may have multiple checks - all checks must pass for the command to be executed. + * A slash command may have multiple checks - all checks must pass for the command to be executed. * Checks will be run in the order that they're defined. * * This function can be used DSL-style with a given body, or it can be passed one or more @@ -912,17 +912,17 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun check(vararg checks: Check) { - checks.forEach { checkList.add(it) } + public fun slashCheck(vararg checks: Check) { + checks.forEach { slashCheckList.add(it) } } /** - * Overloaded check function to allow for DSL syntax. + * Overloaded slash command check function to allow for DSL syntax. * * @param check Check to apply to all slash commands. */ - public fun check(check: Check) { - checkList.add(check) + public fun slashCheck(check: Check) { + slashCheckList.add(check) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/_Types.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/_Types.kt index 8cde40f2a2..320dc60fdf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/_Types.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/_Types.kt @@ -2,5 +2,22 @@ package com.kotlindiscord.kord.extensions.checks.types +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import dev.kord.core.event.message.MessageCreateEvent + /** Types alias representing a check function for a specific event type. **/ public typealias Check = suspend CheckContext.() -> Unit + +/** Check type for chat commands. **/ +public typealias ChatCommandCheck = Check + +/** Check type for message commands. **/ +public typealias MessageCommandCheck = Check + +/** Check type for slash commands. **/ +public typealias SlashCommandCheck = Check + +/** Check type for user commands. **/ +public typealias UserCommandCheck = Check diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index f93f1aa9cf..7ea22347cd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -193,6 +193,10 @@ public abstract class ApplicationCommand( /** Called in order to execute the command. **/ public open suspend fun doCall(event: E) { + if (!runChecks(event)) { + return + } + call(event) } 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 253c7496a3..1f5f21b452 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 @@ -1,8 +1,11 @@ package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand +import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.core.Kord +import dev.kord.core.entity.application.UserCommand import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -17,8 +20,18 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() - /** Register an application command. **/ - public open suspend fun register() { + /** Register a message command. **/ + public open suspend fun register(command: MessageCommand<*>) { + TODO() + } + + /** Register a slash command. **/ + public open suspend fun register(command: SlashCommand<*>) { + TODO() + } + + /** Register a user command. **/ + public open suspend fun register(command: UserCommand) { TODO() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 143c1cb623..79b21bdcb4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -5,7 +5,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent /** Slash command, executed directly in the chat input. **/ -public abstract class SlashCommand( +public open class SlashCommand( extension: Extension ) : ApplicationCommand(extension) { /** Command description, to explain what your command does. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index c7c45588f9..aca070dea5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -5,7 +5,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent /** User context command, for right-click actions on users. **/ -public abstract class UserCommand( +public open class UserCommand( extension: Extension ) : ApplicationCommand(extension) { override suspend fun call(event: UserCommandInteractionCreateEvent) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt index f4569722e0..b5e9b0f093 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt @@ -91,7 +91,7 @@ public open class SlashCommand( /** Guild ID this slash command is to be registered for, if any. **/ public open var guild: Snowflake? = if (parentCommand == null && parentGroup == null) { - settings.slashCommandsBuilder.defaultGuild + settings.applicationCommandsBuilder.defaultGuild } else { null } @@ -442,7 +442,7 @@ public open class SlashCommand( val locale = event.getLocale() // global checks - for (check in extension.bot.settings.slashCommandsBuilder.checkList) { + for (check in extension.bot.settings.applicationCommandsBuilder.slashCheckList) { val context = CheckContext(event, locale) check(context) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt index 831d44b122..b5b6074c07 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt @@ -94,7 +94,7 @@ public open class SlashCommandRegistry : KoinComponent { public open suspend fun syncAll() { logger.info { "Synchronising slash commands. This may take some time." } - if (!bot.settings.slashCommandsBuilder.register) { + if (!bot.settings.applicationCommandsBuilder.register) { logger.debug { "Slash command registration is disabled, pairing existing commands with extension commands." } @@ -140,7 +140,7 @@ public open class SlashCommandRegistry : KoinComponent { kord.unsafe.guild(guild).commands.map { Pair(it.name, it.id) }.toList() } - if (!bot.settings.slashCommandsBuilder.register) { + if (!bot.settings.applicationCommandsBuilder.register) { registered.forEach { r -> val existingCommand = existing.firstOrNull { it.first == r.getTranslatedName(locale) } diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index d7be02be70..06c67e7fd6 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -23,7 +23,7 @@ suspend fun main() { } } - slashCommands { + applicationCommands { enabled = true } diff --git a/modules/java-time/build.gradle.kts b/modules/java-time/build.gradle.kts index fa54c06faa..6e2ee15094 100644 --- a/modules/java-time/build.gradle.kts +++ b/modules/java-time/build.gradle.kts @@ -3,6 +3,7 @@ import java.net.URL plugins { `maven-publish` + signing kotlin("jvm") kotlin("plugin.serialization") @@ -95,6 +96,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() diff --git a/modules/time4j/build.gradle.kts b/modules/time4j/build.gradle.kts index b4223eff59..d84dea15e0 100644 --- a/modules/time4j/build.gradle.kts +++ b/modules/time4j/build.gradle.kts @@ -3,6 +3,7 @@ import java.net.URL plugins { `maven-publish` + signing kotlin("jvm") @@ -97,6 +98,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() diff --git a/token-parser/build.gradle.kts b/token-parser/build.gradle.kts index 2e9c01e352..1f027263ee 100644 --- a/token-parser/build.gradle.kts +++ b/token-parser/build.gradle.kts @@ -3,6 +3,7 @@ import java.net.URL plugins { `maven-publish` + signing kotlin("jvm") @@ -82,6 +83,11 @@ publishing { } } +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + fun runCommand(command: String): String { val output = ByteArrayOutputStream() From f4e5eef77fb06bf6f0940cc8540d926406a47772 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 29 Aug 2021 11:31:48 +0100 Subject: [PATCH 015/131] Grouped commands parsing fix, thanks to @kimcore --- .../kord/extensions/commands/chat/ChatGroupCommand.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index 9719eae11b..bec7e3251e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -204,7 +204,6 @@ public open class ChatGroupCommand( } val command = parser.peekNext()?.data?.lowercase() - val remainingArgs = parser.consumeRemaining() val subCommand = getCommand(command, event) if (subCommand == null) { @@ -212,7 +211,7 @@ public open class ChatGroupCommand( } else { parser.parseNext() // Advance the cursor so proper parsing can happen - subCommand.call(event, commandName, StringParser(remainingArgs), argString) + subCommand.call(event, commandName, StringParser(parser.consumeRemaining()), argString) } } From f89e0f4835cab1edc60ff5eade372f6bdb7c2500 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 29 Aug 2021 13:28:47 +0100 Subject: [PATCH 016/131] Theoretically working message commands, no impl yet --- .../EphemeralApplicationCommandContext.kt | 9 ++ .../PublicApplicationCommandContext.kt | 10 ++ .../message/EphemeralMessageCommand.kt | 52 +++++++- .../message/EphemeralMessageCommandContext.kt | 4 +- .../application/message/MessageCommand.kt | 119 +++++++++++++++++- .../message/MessageCommandContext.kt | 4 +- .../message/PublicMessageCommand.kt | 52 +++++++- .../message/PublicMessageCommandContext.kt | 4 +- 8 files changed, 243 insertions(+), 11 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt index 0092ea676b..63425df906 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt @@ -1,9 +1,11 @@ package com.kotlindiscord.kord.extensions.commands.application import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUpEphemeral import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder /** Interface representing an ephemeral-only application command context. **/ public interface EphemeralApplicationCommandContext { @@ -19,3 +21,10 @@ public interface EphemeralApplicationCommandContext { public suspend inline fun EphemeralApplicationCommandContext.respond( builder: EphemeralFollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) + +/** + * Edit the current interaction's response, if one was sent via `initialResponse`. + */ +public suspend inline fun EphemeralApplicationCommandContext.edit( + builder: EphemeralInteractionResponseModifyBuilder.() -> Unit +): Unit? = (interactionResponse as? EphemeralInteractionResponseBehavior)?.edit(builder) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt index 3a8570bb7e..8d15d1555e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt @@ -1,9 +1,12 @@ package com.kotlindiscord.kord.extensions.commands.application import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.entity.Message import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder /** Interface representing a public-only application command context. **/ public interface PublicApplicationCommandContext { @@ -15,3 +18,10 @@ public interface PublicApplicationCommandContext { public suspend inline fun PublicApplicationCommandContext.respond( builder: PublicFollowupMessageCreateBuilder.() -> Unit ): PublicFollowupMessage = interactionResponse.followUp(builder) + +/** + * Edit the current interaction's response, if one was sent via `initialResponse`. + */ +public suspend inline fun PublicApplicationCommandContext.edit( + builder: PublicInteractionResponseModifyBuilder.() -> Unit +): Message? = (interactionResponse as? PublicInteractionResponseBehavior)?.edit(builder) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index da971b65ba..534666da1a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -1,8 +1,58 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.commands.application.message +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder + +public typealias InitialEphemeralResponseBuilder = + (suspend EphemeralInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? /** Ephemeral-followup-only message command. **/ public class EphemeralMessageCommand( extension: Extension -) : MessageCommand(extension) +) : MessageCommand(extension) { + /** Provide this tn open with a response, omit it to ack instead. **/ + public var initialResponseBuilder: InitialEphemeralResponseBuilder = null + + override suspend fun call(event: MessageCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondEphemeral { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgeEphemeral() + } + + val context = EphemeralMessageCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: EphemeralMessageCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt index 66e5ca208f..106a070e8f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt @@ -7,6 +7,6 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent /** Ephemeral-only message command context. **/ public class EphemeralMessageCommandContext( override val event: MessageCommandInteractionCreateEvent, - override val command: MessageCommand, + override val command: MessageCommand, override val interactionResponse: EphemeralInteractionResponseBehavior -) : MessageCommandContext(event, command), EphemeralApplicationCommandContext +) : MessageCommandContext(event, command), EphemeralApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 985e662938..098a529384 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -1,13 +1,27 @@ package com.kotlindiscord.kord.extensions.commands.application.message +import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.tag +import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.permissionsForMember +import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.core.entity.channel.DmChannel +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging /** Message context command, for right-click actions on messages. **/ -public open class MessageCommand( +public abstract class MessageCommand>( extension: Extension ) : ApplicationCommand(extension) { + private val logger: KLogger = KotlinLogging.logger {} + /** Command body, to be called when the command is executed. **/ public lateinit var body: suspend C.() -> Unit @@ -16,7 +30,106 @@ public open class MessageCommand( body = action } - override suspend fun call(event: MessageCommandInteractionCreateEvent) { - TODO("Not yet implemented") + /** Override this to implement your command's calling logic. Check subtypes for examples! **/ + public abstract override suspend fun call(event: MessageCommandInteractionCreateEvent) + + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ + public abstract suspend fun respondText(context: C, message: String) + + /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ + @Throws(CommandException::class) + public open suspend fun checkBotPerms(context: C) { + if (context.guild != null) { + val perms = (context.channel.asChannel() as GuildChannel) + .permissionsForMember(kord.selfId) + + val missingPerms = requiredPerms.filter { !perms.contains(it) } + + if (missingPerms.isNotEmpty()) { + throw CommandException( + context.translate( + "commands.error.missingBotPermissions", + null, + + replacements = arrayOf( + missingPerms.map { it.translate(context.getLocale()) }.joinToString(", ") + ) + ) + ) + } + } + } + + /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ + public open suspend fun firstSentryBreadcrumb(context: C) { + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "command.application.message" + message = "Message command \"$name\" called." + + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() + + data["command"] = name + + if (guildId != null) { + data["command.guild"] = guildId!!.asString + } + + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } + } + + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } + } + } + } + + /** A general way to handle errors thrown during the course of a command's execution. **/ + public open suspend fun handleError(context: C, t: Throwable) { + logger.error(t) { "Error during execution of $name message command (${context.event})" } + + if (sentry.enabled) { + logger.debug { "Submitting error to sentry." } + + val channel = context.channel + val author = context.user.asUserOrNull() + + val sentryId = context.sentry.captureException(t, "Message command execution failed.") { + if (author != null) { + user(author) + } + + tag("private", "false") + + if (channel is DmChannel) { + tag("private", "true") + } + + tag("command", name) + tag("extension", extension.name) + + Sentry.captureException(t, "Message command execution failed.") + } + + logger.debug { "Error submitted to Sentry: $sentryId" } + + val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { + context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + } else { + context.translate("commands.error.user", null) + } + + respondText(context, errorMessage) + } else { + respondText(context, context.translate("commands.error.user", null)) + } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt index e1d633744d..fa5a727296 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt @@ -10,9 +10,9 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent * @param event Event that triggered this message command. * @param command Message command instance. */ -public open class MessageCommandContext( +public abstract class MessageCommandContext>( public open val event: MessageCommandInteractionCreateEvent, - public open val command: MessageCommand, + public open val command: MessageCommand, ) : ApplicationCommandContext(event, command) { /** Messages that this message command is being executed against. **/ public val targetMessages: Collection = event.interaction.messages?.values ?: listOf() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 67206d191a..7e85336633 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -1,8 +1,58 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.commands.application.message +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder + +public typealias InitialPublicResponseBuilder = + (suspend PublicInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? /** Public-followup-only message command. **/ public class PublicMessageCommand( extension: Extension -) : MessageCommand(extension) +) : MessageCommand(extension) { + /** Provide this tn open with a response, omit it to ack instead. **/ + public var initialResponseBuilder: InitialPublicResponseBuilder = null + + override suspend fun call(event: MessageCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgePublic() + } + + val context = PublicMessageCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: PublicMessageCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt index 0e06a5be35..b5f6beef1c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt @@ -7,6 +7,6 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent /** Public-only message command context. **/ public class PublicMessageCommandContext( override val event: MessageCommandInteractionCreateEvent, - override val command: MessageCommand, + override val command: MessageCommand, override val interactionResponse: PublicInteractionResponseBehavior -) : MessageCommandContext(event, command), PublicApplicationCommandContext +) : MessageCommandContext(event, command), PublicApplicationCommandContext From b2e3ede9eba15112d12c0b2638f0f9ed3432b149 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 29 Aug 2021 13:40:55 +0100 Subject: [PATCH 017/131] Theoretically working user commands, no impl yet --- .../message/EphemeralMessageCommand.kt | 6 +- .../message/PublicMessageCommand.kt | 6 +- .../application/user/EphemeralUserCommand.kt | 58 ++++++++ .../user/EphemeralUserCommandContext.kt | 12 ++ .../application/user/PublicUserCommand.kt | 58 ++++++++ .../user/PublicUserCommandContext.kt | 12 ++ .../commands/application/user/UserCommand.kt | 127 +++++++++++++++++- .../application/user/UserCommandContext.kt | 12 +- 8 files changed, 278 insertions(+), 13 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index 534666da1a..2b04c5430b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -9,15 +9,15 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder -public typealias InitialEphemeralResponseBuilder = +public typealias InitialEphemeralMessageResponseBuilder = (suspend EphemeralInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? -/** Ephemeral-followup-only message command. **/ +/** Ephemeral message command. **/ public class EphemeralMessageCommand( extension: Extension ) : MessageCommand(extension) { /** Provide this tn open with a response, omit it to ack instead. **/ - public var initialResponseBuilder: InitialEphemeralResponseBuilder = null + public var initialResponseBuilder: InitialEphemeralMessageResponseBuilder = null override suspend fun call(event: MessageCommandInteractionCreateEvent) { try { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 7e85336633..2b2f40387e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -9,15 +9,15 @@ import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder -public typealias InitialPublicResponseBuilder = +public typealias InitialPublicMessageResponseBuilder = (suspend PublicInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? -/** Public-followup-only message command. **/ +/** Public message command. **/ public class PublicMessageCommand( extension: Extension ) : MessageCommand(extension) { /** Provide this tn open with a response, omit it to ack instead. **/ - public var initialResponseBuilder: InitialPublicResponseBuilder = null + public var initialResponseBuilder: InitialPublicMessageResponseBuilder = null override suspend fun call(event: MessageCommandInteractionCreateEvent) { try { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt new file mode 100644 index 0000000000..752277a31f --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -0,0 +1,58 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.respond +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder + +public typealias InitialEphemeralUserResponseBuilder = + (suspend EphemeralInteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? + +/** Ephemeral user command. **/ +public class EphemeralUserCommand( + extension: Extension +) : UserCommand(extension) { + /** Provide this tn open with a response, omit it to ack instead. **/ + public var initialResponseBuilder: InitialEphemeralUserResponseBuilder = null + + override suspend fun call(event: UserCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondEphemeral { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgeEphemeral() + } + + val context = EphemeralUserCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: EphemeralUserCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt new file mode 100644 index 0000000000..bdbf150a9a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** Ephemeral-only user command context. **/ +public class EphemeralUserCommandContext( + override val event: UserCommandInteractionCreateEvent, + override val command: UserCommand, + override val interactionResponse: EphemeralInteractionResponseBehavior +) : UserCommandContext(event, command), EphemeralApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt new file mode 100644 index 0000000000..3bf0b0ab9d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -0,0 +1,58 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.respond +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder + +public typealias InitialPublicUserResponseBuilder = + (suspend PublicInteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? + +/** Public user command. **/ +public class PublicUserCommand( + extension: Extension +) : UserCommand(extension) { + /** Provide this tn open with a response, omit it to ack instead. **/ + public var initialResponseBuilder: InitialPublicUserResponseBuilder = null + + override suspend fun call(event: UserCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgePublic() + } + + val context = PublicUserCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: PublicUserCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt new file mode 100644 index 0000000000..42cde6fcb0 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.commands.application.user + +import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** Public-only user command context. **/ +public class PublicUserCommandContext( + override val event: UserCommandInteractionCreateEvent, + override val command: UserCommand, + override val interactionResponse: PublicInteractionResponseBehavior +) : UserCommandContext(event, command), PublicApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index aca070dea5..9bc25318ca 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -1,14 +1,135 @@ package com.kotlindiscord.kord.extensions.commands.application.user +import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.tag +import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.permissionsForMember +import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.core.entity.channel.DmChannel +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging /** User context command, for right-click actions on users. **/ -public open class UserCommand( +public abstract class UserCommand>( extension: Extension ) : ApplicationCommand(extension) { - override suspend fun call(event: UserCommandInteractionCreateEvent) { - TODO("Not yet implemented") + private val logger: KLogger = KotlinLogging.logger {} + + /** Command body, to be called when the command is executed. **/ + public lateinit var body: suspend C.() -> Unit + + /** Call this to supply a command [body], to be called when the command is executed. **/ + public fun action(action: suspend C.() -> Unit) { + body = action + } + + /** Override this to implement your command's calling logic. Check subtypes for examples! **/ + public abstract override suspend fun call(event: UserCommandInteractionCreateEvent) + + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ + public abstract suspend fun respondText(context: C, message: String) + + /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ + @Throws(CommandException::class) + public open suspend fun checkBotPerms(context: C) { + if (context.guild != null) { + val perms = (context.channel.asChannel() as GuildChannel) + .permissionsForMember(kord.selfId) + + val missingPerms = requiredPerms.filter { !perms.contains(it) } + + if (missingPerms.isNotEmpty()) { + throw CommandException( + context.translate( + "commands.error.missingBotPermissions", + null, + + replacements = arrayOf( + missingPerms.map { it.translate(context.getLocale()) }.joinToString(", ") + ) + ) + ) + } + } + } + + /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ + public open suspend fun firstSentryBreadcrumb(context: C) { + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "command.application.user" + message = "User command \"$name\" called." + + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() + + data["command"] = name + + if (guildId != null) { + data["command.guild"] = guildId!!.asString + } + + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } + } + + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } + } + } + } + + /** A general way to handle errors thrown during the course of a command's execution. **/ + public open suspend fun handleError(context: C, t: Throwable) { + logger.error(t) { "Error during execution of $name user command (${context.event})" } + + if (sentry.enabled) { + logger.debug { "Submitting error to sentry." } + + val channel = context.channel + val author = context.user.asUserOrNull() + + val sentryId = context.sentry.captureException(t, "User command execution failed.") { + if (author != null) { + user(author) + } + + tag("private", "false") + + if (channel is DmChannel) { + tag("private", "true") + } + + tag("command", name) + tag("extension", extension.name) + + Sentry.captureException(t, "User command execution failed.") + } + + logger.debug { "Error submitted to Sentry: $sentryId" } + + val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { + context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + } else { + context.translate("commands.error.user", null) + } + + respondText(context, errorMessage) + } else { + respondText(context, context.translate("commands.error.user", null)) + } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt index 47d920985a..39148dd07a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt @@ -1,6 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext +import dev.kord.core.entity.User import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent /** @@ -9,7 +10,10 @@ import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent * @param event Event that triggered this message command. * @param command Message command instance. */ -public class UserCommandContext( - event: UserCommandInteractionCreateEvent, - command: UserCommand -) : ApplicationCommandContext(event, command) +public abstract class UserCommandContext>( + public open val event: UserCommandInteractionCreateEvent, + public open val command: UserCommand +) : ApplicationCommandContext(event, command) { + /** Messages that this message command is being executed against. **/ + public val targetUsers: Collection = event.interaction.users?.values ?: listOf() +} From ce7f88e11dcaedacdaf42a1148964b0a108f523c Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 29 Aug 2021 17:40:25 +0100 Subject: [PATCH 018/131] Kotlin 1.5.30, big command refactoring --- .../converters/ConverterProcessor.kt | 2 +- .../extra/mappings/MappingsExtension.kt | 2 +- .../mappings/arguments/LegacyYarnArguments.kt | 2 +- .../extra/mappings/arguments/MCPArguments.kt | 2 +- .../mappings/arguments/MojangArguments.kt | 2 +- .../mappings/arguments/PlasmaArguments.kt | 2 +- .../extra/mappings/arguments/YarnArguments.kt | 2 +- .../mappings/arguments/YarrnArguments.kt | 2 +- .../converters/MappingsVersionConverter.kt | 4 +- .../kord/extensions/ExtensibleBot.kt | 2 +- .../commands/{parser => }/Argument.kt | 2 +- .../commands/{parser => }/Arguments.kt | 5 +- .../kord/extensions/commands/Command.kt | 10 +- .../extensions/commands/CommandContext.kt | 7 - .../application/ApplicationCommand.kt | 29 +- .../application/ApplicationCommandContext.kt | 79 +- .../application/ApplicationCommandRegistry.kt | 8 +- .../message/EphemeralMessageCommand.kt | 7 +- .../application/message/MessageCommand.kt | 9 + .../message/MessageCommandContext.kt | 2 +- .../message/PublicMessageCommand.kt | 7 +- .../slash/EphemeralSlashCommand.kt | 103 +++ .../slash/EphemeralSlashCommandContext.kt | 13 + .../application/slash/PublicSlashCommand.kt | 103 +++ .../slash/PublicSlashCommandContext.kt | 13 + .../application/slash/SlashCommand.kt | 195 ++++- .../application/slash/SlashCommandContext.kt | 26 +- .../slash}/SlashCommandParser.kt | 28 +- .../commands/application/slash/SlashGroup.kt | 39 + .../commands/application/slash/_Functions.kt | 317 ++++++++ .../slash/converters/ChoiceConverter.kt | 2 +- .../slash/converters/ChoiceEnum.kt | 4 +- .../converters/impl/EnumChoiceConverter.kt | 10 +- .../converters/impl/NumberChoiceConverter.kt | 6 +- .../converters/impl/StringChoiceConverter.kt | 6 +- .../application/user/EphemeralUserCommand.kt | 7 +- .../application/user/PublicUserCommand.kt | 7 +- .../commands/application/user/UserCommand.kt | 9 + .../application/user/UserCommandContext.kt | 2 +- .../extensions/commands/chat/ChatCommand.kt | 15 +- .../commands/chat/ChatCommandContext.kt | 15 +- .../ChatCommandParser.kt} | 8 +- .../commands/chat/ChatCommandRegistry.kt | 5 +- .../commands/chat/ChatGroupCommand.kt | 2 +- .../commands/chat/ChatSubCommand.kt | 2 +- .../CoalescingToDefaultingConverter.kt | 2 +- .../CoalescingToOptionalConverter.kt | 2 +- .../commands/converters/Converter.kt | 4 +- .../converters/SingleToDefaultingConverter.kt | 2 +- .../converters/SingleToMultiConverter.kt | 2 +- .../converters/SingleToOptionalConverter.kt | 2 +- .../converters/SlashCommandConverter.kt | 2 +- .../extensions/commands/converters/_Types.kt | 2 +- .../converters/impl/BooleanConverter.kt | 2 +- .../converters/impl/ChannelConverter.kt | 2 +- .../converters/impl/ColorConverter.kt | 2 +- .../converters/impl/DecimalConverter.kt | 2 +- .../impl/DurationCoalescingConverter.kt | 2 +- .../converters/impl/DurationConverter.kt | 2 +- .../converters/impl/EmailConverter.kt | 2 +- .../converters/impl/EmojiConverter.kt | 2 +- .../commands/converters/impl/EnumConverter.kt | 2 +- .../converters/impl/GuildConverter.kt | 2 +- .../commands/converters/impl/IntConverter.kt | 2 +- .../commands/converters/impl/LongConverter.kt | 2 +- .../converters/impl/MemberConverter.kt | 7 +- .../converters/impl/MessageConverter.kt | 7 +- .../impl/RegexCoalescingConverter.kt | 2 +- .../converters/impl/RegexConverter.kt | 2 +- .../commands/converters/impl/RoleConverter.kt | 2 +- .../converters/impl/SnowflakeConverter.kt | 2 +- .../impl/StringCoalescingConverter.kt | 2 +- .../converters/impl/StringConverter.kt | 2 +- .../converters/impl/SupportedLocale.kt | 2 +- .../converters/impl/UnionConverter.kt | 4 +- .../commands/converters/impl/UserConverter.kt | 7 +- .../extensions/commands/slash/Annotations.kt | 13 - .../extensions/commands/slash/SlashCommand.kt | 726 ------------------ .../commands/slash/SlashCommandContext.kt | 214 ------ .../commands/slash/SlashCommandRegistry.kt | 18 +- .../extensions/commands/slash/SlashGroup.kt | 104 --- .../slash => components}/AutoAckType.kt | 2 +- .../kord/extensions/components/Components.kt | 5 +- .../builders/ActionableComponentBuilder.kt | 9 +- .../components/builders/ComponentBuilder.kt | 4 +- .../kord/extensions/extensions/Extension.kt | 24 +- .../kord/extensions/extensions/_Commands.kt | 298 +++++-- .../extensions/base/HelpProvider.kt | 2 +- .../extensions/impl/HelpExtension.kt | 2 +- .../extensions/impl/SentryExtension.kt | 14 +- .../pagination/InteractionButtonPaginator.kt | 23 +- .../kord/extensions/pagination/Paginator.kt | 352 --------- .../kord/extensions/sentry/Converters.kt | 2 +- .../extensions/sentry/SentryIdConverter.kt | 2 +- .../extensions/test/bot/TestChoiceEnum.kt | 2 +- .../kord/extensions/test/bot/TestExtension.kt | 224 +++--- libs.versions.toml | 6 +- .../java/J8DurationCoalescingConverter.kt | 4 +- .../modules/time/java/J8DurationConverter.kt | 4 +- .../kord/extensions/test/bot/TestExtension.kt | 2 +- .../time4j/T4JDurationCoalescingConverter.kt | 4 +- .../time/time4j/T4JDurationConverter.kt | 4 +- .../kord/extensions/test/bot/TestExtension.kt | 2 +- settings.gradle.kts | 6 +- 104 files changed, 1400 insertions(+), 1877 deletions(-) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{parser => }/Argument.kt (90%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{parser => }/Arguments.kt (97%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{slash/parser => application/slash}/SlashCommandParser.kt (92%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{ => application}/slash/converters/ChoiceConverter.kt (89%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{ => application}/slash/converters/ChoiceEnum.kt (51%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{ => application}/slash/converters/impl/EnumChoiceConverter.kt (90%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{ => application}/slash/converters/impl/NumberChoiceConverter.kt (91%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{ => application}/slash/converters/impl/StringChoiceConverter.kt (88%) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/{parser/ArgumentParser.kt => chat/ChatCommandParser.kt} (98%) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/Annotations.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashGroup.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/{commands/slash => components}/AutoAckType.kt (84%) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/Paginator.kt diff --git a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/ConverterProcessor.kt b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/ConverterProcessor.kt index de6ca2eaae..9abb8d280e 100644 --- a/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/ConverterProcessor.kt +++ b/annotation-processor/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/annotations/converters/ConverterProcessor.kt @@ -228,7 +228,7 @@ public class ConverterProcessor( // Imports that all converters need import com.kotlindiscord.kord.extensions.commands.converters.* - import com.kotlindiscord.kord.extensions.commands.parser.Arguments + import com.kotlindiscord.kord.extensions.commands.Arguments import dev.kord.common.annotation.KordPreview diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index 9b617529f1..9c856bb3ce 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -4,8 +4,8 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings import com.kotlindiscord.kord.extensions.checks.and import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments.* diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/LegacyYarnArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/LegacyYarnArguments.kt index 438099c667..c73ffd8114 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/LegacyYarnArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/LegacyYarnArguments.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import me.shedaniel.linkie.namespaces.LegacyYarnNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MCPArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MCPArguments.kt index 3b5370c40b..66027a3722 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MCPArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MCPArguments.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import me.shedaniel.linkie.namespaces.MCPNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MojangArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MojangArguments.kt index b0edd449aa..5c6dbb7f4c 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MojangArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/MojangArguments.kt @@ -1,8 +1,8 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalEnum import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import com.kotlindiscord.kord.extensions.modules.extra.mappings.enums.Channels import me.shedaniel.linkie.namespaces.MojangNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/PlasmaArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/PlasmaArguments.kt index cbbbc9706d..92da012a79 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/PlasmaArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/PlasmaArguments.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import me.shedaniel.linkie.namespaces.PlasmaNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarnArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarnArguments.kt index 6138b3e317..3ca311dcfb 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarnArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarnArguments.kt @@ -1,8 +1,8 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalEnum import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import com.kotlindiscord.kord.extensions.modules.extra.mappings.enums.YarnChannels import me.shedaniel.linkie.namespaces.YarnNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarrnArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarrnArguments.kt index d9981830af..1d1aa889a9 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarrnArguments.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/YarrnArguments.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion import me.shedaniel.linkie.namespaces.YarrnNamespace diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt index 0b6308014f..91321b4175 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt @@ -3,12 +3,12 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.converters import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.ConverterToOptional import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index af3c7b8ff6..ee1216f637 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -3,9 +3,9 @@ package com.kotlindiscord.kord.extensions import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.events.ExtensionEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Argument.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Argument.kt similarity index 90% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Argument.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Argument.kt index 032dd7018f..338b6777d6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Argument.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Argument.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.parser +package com.kotlindiscord.kord.extensions.commands import com.kotlindiscord.kord.extensions.commands.converters.Converter diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Arguments.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt similarity index 97% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Arguments.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt index 0741bc168f..9bca939d9c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/Arguments.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt @@ -1,9 +1,6 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.commands.parser +package com.kotlindiscord.kord.extensions.commands import com.kotlindiscord.kord.extensions.commands.converters.* -import dev.kord.common.annotation.KordPreview /** * Abstract base class for a class containing a set of command arguments. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt index cbe9cead88..25c34c0c85 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt @@ -1,8 +1,9 @@ +@file:Suppress("UnnecessaryAbstractClass") // No idea why we're getting this + package com.kotlindiscord.kord.extensions.commands import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.commands.parser.ArgumentParser import com.kotlindiscord.kord.extensions.extensions.Extension /** @@ -19,11 +20,6 @@ public abstract class Command(public val extension: Extension) { */ public open lateinit var name: String - /** - * Argument parser object responsible for transforming arguments into Kotlin types. - */ - public abstract val parser: ArgumentParser - /** * An internal function used to ensure that all of a command's required arguments are present. * @@ -31,7 +27,7 @@ public abstract class Command(public val extension: Extension) { */ @Throws(InvalidCommandException::class) public open fun validate() { - if (!::name.isInitialized) { + if (!::name.isInitialized || name.isEmpty()) { throw InvalidCommandException(null, "No command name given.") } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt index 08b4fd89ff..3bfe3e9de5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/CommandContext.kt @@ -5,11 +5,9 @@ import com.kotlindiscord.kord.extensions.checks.channelFor import com.kotlindiscord.kord.extensions.checks.guildFor import com.kotlindiscord.kord.extensions.checks.userFor import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.sentry.SentryContext import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior -import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.event.Event @@ -26,14 +24,12 @@ import java.util.* * @param command Respective command for this context object. * @param eventObj Event that triggered this command. * @param commandName Command name given by the user to invoke the command - lower-cased. - * @param parser String parser instance, if any - will be `null` if this isn't a message command. */ @ExtensionDSL public abstract class CommandContext( public open val command: Command, public open val eventObj: Event, public open val commandName: String, - public open val parser: StringParser?, ) : KoinComponent { /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() @@ -56,9 +52,6 @@ public abstract class CommandContext( /** Extract member information from event data, if that context is available. **/ public abstract suspend fun getMember(): MemberBehavior? - /** Extract message information from event data, if that context is available. **/ - public abstract suspend fun getMessage(): MessageBehavior? - /** Extract user information from event data, if that context is available. **/ public abstract suspend fun getUser(): UserBehavior? diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 7ea22347cd..22a250af53 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -1,8 +1,10 @@ package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.commands.Command import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.sentry.SentryAdapter @@ -25,11 +27,17 @@ import java.util.* * @param extension Extension this application command belongs to. */ public abstract class ApplicationCommand( - public open val extension: Extension -) : KoinComponent { + extension: Extension +) : Command(extension), KoinComponent { /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() + /** Quick access to the command registry. **/ + public val registry: ApplicationCommandRegistry by inject() + + /** Bot settings object. **/ + public val settings: ExtensibleBotBuilder by inject() + /** Kord instance, backing the ExtensibleBot. **/ public val kord: Kord by inject() @@ -40,7 +48,7 @@ public abstract class ApplicationCommand( public open val checkList: MutableList> = mutableListOf() /** @suppress **/ - public open var guildId: Snowflake? = null + public open var guildId: Snowflake? = settings.applicationCommandsBuilder.defaultGuild /** * Whether to allow everyone to use this command by default. @@ -78,9 +86,6 @@ public abstract class ApplicationCommand( /** Translation cache, so we don't have to look up translations every time. **/ public open val nameTranslationCache: MutableMap = mutableMapOf() - /** Command name, shown on Discord. **/ - public lateinit var name: String - /** Return this command's name translated for the given locale, cached as required. **/ public open fun getTranslatedName(locale: Locale): String { if (!nameTranslationCache.containsKey(locale)) { @@ -179,18 +184,6 @@ public abstract class ApplicationCommand( checkList.add(check) } - /** Override this in your subclass if you need to change how the command name is validated. **/ - public open fun validateName() { - if (::name.isInitialized.not() || name.isEmpty()) { - error("Application command names are required.") - } - } - - /** General command validation function. Can be overridden. **/ - public open fun validate() { - validateName() - } - /** Called in order to execute the command. **/ public open suspend fun doCall(event: E) { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt index 8046de5e40..7036e0a7d7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -1,20 +1,14 @@ package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.checks.channelFor -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.userFor -import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.sentry.SentryContext +import com.kotlindiscord.kord.extensions.commands.CommandContext import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent -import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.util.* /** * Base class representing the shared functionality for an application command's context. @@ -25,19 +19,10 @@ import java.util.* public abstract class ApplicationCommandContext( public val genericEvent: ApplicationInteractionCreateEvent, public val genericCommand: ApplicationCommand<*> -) : KoinComponent { +) : CommandContext(genericCommand, genericEvent, genericCommand.name) { /** Current bot setting object. **/ public val botSettings: ExtensibleBotBuilder by inject() - /** Translations provider, for retrieving translations. **/ - public val translationsProvider: TranslationsProvider by inject() - - /** Current Sentry context, containing breadcrumbs and other goodies. **/ - public val sentry: SentryContext = SentryContext() - - /** Cached locale variable, stored and retrieved by [getLocale]. **/ - public open var resolvedLocale: Locale? = null - /** Channel this command was executed within. **/ public open lateinit var channel: MessageChannelBehavior @@ -51,7 +36,7 @@ public abstract class ApplicationCommandContext( public open lateinit var user: UserBehavior /** Called before processing, used to populate any extra variables from event data. **/ - public open suspend fun populate() { + public override suspend fun populate() { // NOTE: This must always be alphabetical, some latter calls rely on earlier ones channel = getChannel() @@ -61,68 +46,18 @@ public abstract class ApplicationCommandContext( } /** Extract channel information from event data, if that context is available. **/ - public open suspend fun getChannel(): MessageChannelBehavior = + public override suspend fun getChannel(): MessageChannelBehavior = genericEvent.interaction.getChannel() /** Extract guild information from event data, if that context is available. **/ - public open suspend fun getGuild(): GuildBehavior? = + public override suspend fun getGuild(): GuildBehavior? = (channel as? GuildMessageChannel)?.guild /** Extract member information from event data, if that context is available. **/ - public open suspend fun getMember(): MemberBehavior? = + public override suspend fun getMember(): MemberBehavior? = guild?.getMember(genericEvent.interaction.user.id) /** Extract user information from event data, if that context is available. **/ - public open suspend fun getUser(): UserBehavior = + public override suspend fun getUser(): UserBehavior = genericEvent.interaction.user - - /** Resolve the locale for this command context. **/ - public suspend fun getLocale(): Locale { - var locale: Locale? = resolvedLocale - - if (locale != null) { - return locale - } - - val guild = guildFor(genericEvent) - val channel = channelFor(genericEvent) - val user = userFor(genericEvent) - - for (resolver in botSettings.i18nBuilder.localeResolvers) { - val result = resolver(guild, channel, user) - - if (result != null) { - locale = result - break - } - } - - resolvedLocale = locale ?: botSettings.i18nBuilder.defaultLocale - - return resolvedLocale!! - } - - /** - * Given a translation key and bundle name, return the translation for the locale provided by the bot's configured - * locale resolvers. - */ - public suspend fun translate( - key: String, - bundleName: String?, - replacements: Array = arrayOf() - ): String { - val locale = getLocale() - - return translationsProvider.translate(key, locale, bundleName, replacements) - } - - /** - * Given a translation key and possible replacements,return the translation for the given locale in the - * extension's configured bundle, for the locale provided by the bot's configured locale resolvers. - */ - public suspend fun translate(key: String, replacements: Array = arrayOf()): String = translate( - key, - genericCommand.extension.bundle, - replacements - ) } 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 1f5f21b452..84de0f18c3 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 @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandParser import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.core.Kord import dev.kord.core.entity.application.UserCommand @@ -17,6 +18,9 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Kord instance, backing the ExtensibleBot. **/ public val kord: Kord by inject() + /** Command parser to use for slash commands. **/ + public open val argumentParser: SlashCommandParser = SlashCommandParser() + /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() @@ -26,7 +30,7 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Register a slash command. **/ - public open suspend fun register(command: SlashCommand<*>) { + public open suspend fun register(command: SlashCommand<*, *>) { TODO() } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index 2b04c5430b..c14f4a09d4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -16,9 +16,14 @@ public typealias InitialEphemeralMessageResponseBuilder = public class EphemeralMessageCommand( extension: Extension ) : MessageCommand(extension) { - /** Provide this tn open with a response, omit it to ack instead. **/ + /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialEphemeralMessageResponseBuilder = null + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralMessageResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: MessageCommandInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 098a529384..9378359ba1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -1,6 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.message import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType @@ -30,6 +31,14 @@ public abstract class MessageCommand>( body = action } + override fun validate() { + super.validate() + + if (!::body.isInitialized) { + throw InvalidCommandException(name, "No command body given.") + } + } + /** Override this to implement your command's calling logic. Check subtypes for examples! **/ public abstract override suspend fun call(event: MessageCommandInteractionCreateEvent) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt index fa5a727296..edb5c36868 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt @@ -12,7 +12,7 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent */ public abstract class MessageCommandContext>( public open val event: MessageCommandInteractionCreateEvent, - public open val command: MessageCommand, + public override val command: MessageCommand, ) : ApplicationCommandContext(event, command) { /** Messages that this message command is being executed against. **/ public val targetMessages: Collection = event.interaction.messages?.values ?: listOf() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 2b2f40387e..2d074ae9db 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -16,9 +16,14 @@ public typealias InitialPublicMessageResponseBuilder = public class PublicMessageCommand( extension: Extension ) : MessageCommand(extension) { - /** Provide this tn open with a response, omit it to ack instead. **/ + /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialPublicMessageResponseBuilder = null + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicMessageResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: MessageCommandInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt new file mode 100644 index 0000000000..fcb1a62147 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -0,0 +1,103 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.respond +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.entity.interaction.GroupCommand +import dev.kord.core.entity.interaction.SubCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder + +public typealias InitialEphemeralChatResponseBuilder = + (suspend EphemeralInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? + +/** Ephemeral slash command. **/ +public class EphemeralSlashCommand( + extension: Extension, + + public override val arguments: (() -> A)? = null, + public override val parentCommand: SlashCommand<*, *>? = null, + public override val parentGroup: SlashGroup? = null +) : SlashCommand, A>(extension) { + /** @suppress Internal guilder **/ + public var initialResponseBuilder: InitialEphemeralChatResponseBuilder = null + + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralChatResponseBuilder) { + initialResponseBuilder = body + } + + override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { + val eventCommand = event.interaction.command + + val commandObj: SlashCommand<*, *> = when (eventCommand) { + is SubCommand -> { + val firstSubCommandKey = eventCommand.name + + this.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + is GroupCommand -> { + val firstEventGroupKey = eventCommand.groupName + val group = this.groups[firstEventGroupKey] ?: error("Unknown command group: $firstEventGroupKey") + val firstSubCommandKey = eventCommand.name + + group.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + else -> this + } + + try { + if (!commandObj.runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondEphemeral { content = e.reason } + + return + } + + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgeEphemeral() + } + + val context = EphemeralSlashCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + + if (arguments != null) { + val args = registry.argumentParser.parse(arguments, context) + + context.populateArgs(args) + } + + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText(context: EphemeralSlashCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt new file mode 100644 index 0000000000..8da3efe7ce --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt @@ -0,0 +1,13 @@ +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +/** Ephemeral-only slash command context. **/ +public class EphemeralSlashCommandContext ( + override val event: ChatInputCommandInteractionCreateEvent, + override val command: SlashCommand, A>, + override val interactionResponse: EphemeralInteractionResponseBehavior +) : SlashCommandContext, A>(event, command), EphemeralApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt new file mode 100644 index 0000000000..176b40a7bd --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -0,0 +1,103 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.respond +import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.interaction.GroupCommand +import dev.kord.core.entity.interaction.SubCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder + +public typealias InitialPublicChatResponseBuilder = + (suspend PublicInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? + +/** Public slash command. **/ +public class PublicSlashCommand( + extension: Extension, + + public override val arguments: (() -> A)? = null, + public override val parentCommand: SlashCommand<*, *>? = null, + public override val parentGroup: SlashGroup? = null +) : SlashCommand, A>(extension) { + /** @suppress Internal guilder **/ + public var initialResponseBuilder: InitialPublicChatResponseBuilder = null + + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicChatResponseBuilder) { + initialResponseBuilder = body + } + + override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { + val eventCommand = event.interaction.command + + val commandObj: SlashCommand<*, *> = when (eventCommand) { + is SubCommand -> { + val firstSubCommandKey = eventCommand.name + + this.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + is GroupCommand -> { + val firstEventGroupKey = eventCommand.groupName + val group = this.groups[firstEventGroupKey] ?: error("Unknown command group: $firstEventGroupKey") + val firstSubCommandKey = eventCommand.name + + group.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + else -> this + } + + try { + if (!commandObj.runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else { + event.interaction.acknowledgePublic() + } + + val context = PublicSlashCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + + if (arguments != null) { + val args = registry.argumentParser.parse(arguments, context) + + context.populateArgs(args) + } + + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText(context: PublicSlashCommandContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt new file mode 100644 index 0000000000..7f1f377b3f --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt @@ -0,0 +1,13 @@ +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +/** Public-only slash command context. **/ +public class PublicSlashCommandContext( + override val event: ChatInputCommandInteractionCreateEvent, + override val command: SlashCommand, A>, + override val interactionResponse: PublicInteractionResponseBehavior +) : SlashCommandContext, A>(event, command), PublicApplicationCommandContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 79b21bdcb4..4a9bd3606d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -1,28 +1,201 @@ package com.kotlindiscord.kord.extensions.commands.application.slash +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.tag +import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.permissionsForMember +import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.channel.DmChannel +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging -/** Slash command, executed directly in the chat input. **/ -public open class SlashCommand( - extension: Extension +/** + * Slash command, executed directly in the chat input. + * + * @param arguments Callable returning an `Arguments` object, if any + * @param parentCommand Parent slash command, if any + * @param parentGroup Parent slash command group, if any + */ +public abstract class SlashCommand, A : Arguments>( + extension: Extension, + + public open val arguments: (() -> A)? = null, + public open val parentCommand: SlashCommand<*, *>? = null, + public open val parentGroup: SlashGroup? = null ) : ApplicationCommand(extension) { - /** Command description, to explain what your command does. **/ - public lateinit var description: String + internal val logger: KLogger = KotlinLogging.logger {} + + /** Command description, as displayed on Discord. **/ + public open lateinit var description: String + + /** Command body, to be called when the command is executed. **/ + public lateinit var body: suspend C.() -> Unit + + /** Whether this command has a body/action set. **/ + public open val hasBody: Boolean get() = ::body.isInitialized + + /** Map of group names to slash command groups, if any. **/ + public open val groups: MutableMap = mutableMapOf() + + /** List of subcommands, if any. **/ + public open val subCommands: MutableList> = mutableListOf() + + override var guildId: Snowflake? = if (parentCommand == null && parentGroup == null) { + settings.applicationCommandsBuilder.defaultGuild + } else { + null + } override fun validate() { super.validate() - if (::description.isInitialized.not() || description.isEmpty()) { - error("Slash command description must be provided.") + if (!::description.isInitialized) { + throw InvalidCommandException(name, "No command description given.") + } + + if (!::body.isInitialized && groups.isEmpty() && subCommands.isEmpty()) { + throw InvalidCommandException(name, "No command action or subcommands/groups given.") + } + + if (::body.isInitialized && !(groups.isEmpty() && subCommands.isEmpty())) { + throw InvalidCommandException( + name, + + "Command action and subcommands/groups given, but slash commands may not have an action if they have" + + " a subcommand or group." + ) + } + + if (parentCommand != null && guildId != null) { + throw InvalidCommandException( + name, + + "Subcommands may not be limited to specific guilds - set the `guild` property on the parent command " + + "instead." + ) + } + } + + /** Call this to supply a command [body], to be called when the command is executed. **/ + public fun action(action: suspend C.() -> Unit) { + body = action + } + + /** Override this to implement your command's calling logic. Check subtypes for examples! **/ + public abstract override suspend fun call(event: ChatInputCommandInteractionCreateEvent) + + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ + public abstract suspend fun respondText(context: C, message: String) + + /** + * Override this to implement the final calling logic, including creating the command context and running with it. + */ + public abstract suspend fun run(event: ChatInputCommandInteractionCreateEvent) + + /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ + @Throws(CommandException::class) + public open suspend fun checkBotPerms(context: C) { + if (context.guild != null) { + val perms = (context.channel.asChannel() as GuildChannel) + .permissionsForMember(kord.selfId) + + val missingPerms = requiredPerms.filter { !perms.contains(it) } + + if (missingPerms.isNotEmpty()) { + throw CommandException( + context.translate( + "commands.error.missingBotPermissions", + null, + + replacements = arrayOf( + missingPerms.map { it.translate(context.getLocale()) }.joinToString(", ") + ) + ) + ) + } } } - override suspend fun runChecks(event: ChatInputCommandInteractionCreateEvent): Boolean = - super.runChecks(event) + /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ + public open suspend fun firstSentryBreadcrumb(context: C, commandObj: SlashCommand<*, *>) { + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "command.application.slash" + message = "Slash command \"${commandObj.name}\" called." + + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() + + data["command"] = commandObj.name + + if (guildId != null) { + data["command.guild"] = guildId!!.asString + } + + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } + } + + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } + } + } + } - override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { - TODO("Not yet implemented") + /** A general way to handle errors thrown during the course of a command's execution. **/ + public open suspend fun handleError(context: C, t: Throwable, commandObj: SlashCommand<*, *>) { + logger.error(t) { "Error during execution of ${commandObj.name} slash command (${context.event})" } + + if (sentry.enabled) { + logger.debug { "Submitting error to sentry." } + + val channel = context.channel + val author = context.user.asUserOrNull() + + val sentryId = context.sentry.captureException(t, "Slash command execution failed.") { + if (author != null) { + user(author) + } + + tag("private", "false") + + if (channel is DmChannel) { + tag("private", "true") + } + + tag("command", commandObj.name) + tag("extension", commandObj.extension.name) + + Sentry.captureException(t, "Slash command execution failed.") + } + + logger.debug { "Error submitted to Sentry: $sentryId" } + + val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { + context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + } else { + context.translate("commands.error.user", null) + } + + respondText(context, errorMessage) + } else { + respondText(context, context.translate("commands.error.user", null)) + } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt index 230b295c2a..1573432ffc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandContext.kt @@ -1,11 +1,23 @@ package com.kotlindiscord.kord.extensions.commands.application.slash -import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandContext -import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -/** Slash command context, containing everything you need for your slash command's execution. **/ -public class SlashCommandContext( - genericEvent: ApplicationInteractionCreateEvent, - genericCommand: ApplicationCommand<*> -) : ApplicationCommandContext(genericEvent, genericCommand) +/** + * Slash command context, containing everything you need for your slash command's execution. + * + * @param event Event that triggered this slash command invocation. + */ +public open class SlashCommandContext, A : Arguments>( + public open val event: ChatInputCommandInteractionCreateEvent, + public override val command: SlashCommand +) : ApplicationCommandContext(event, command) { + /** Object representing this slash command's arguments, if any. **/ + public open lateinit var arguments: A + + /** @suppress Internal function for copying args object in later. **/ + public fun populateArgs(args: A) { + arguments = args + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/parser/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt similarity index 92% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/parser/SlashCommandParser.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt index eddf2ee117..765fbdd008 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/parser/SlashCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt @@ -3,15 +3,12 @@ "StringLiteralDuplication" // Needs cleaning up with polymorphism later anyway ) -package com.kotlindiscord.kord.extensions.commands.slash.parser +package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.ArgumentParser -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext import dev.kord.common.annotation.KordPreview import dev.kord.core.entity.KordEntity import mu.KotlinLogging @@ -27,18 +24,23 @@ private val logger = KotlinLogging.logger {} * This parser does not support multi converters, as there's no good way to represent them with * Discord's API. Coalescing converters will act like single converters. */ -public open class SlashCommandParser : ArgumentParser() { - public override suspend fun parse(builder: () -> T, context: CommandContext): T { - if (context !is SlashCommandContext) { - error("This parser only supports slash commands.") - } - +public open class SlashCommandParser { + /** + * Parse the arguments for this slash command, which have been provided by Discord. + * + * Instead of taking the objects as Discord provides them, this function will stringify all the command's + * arguments. This allows them to be passed through the usual converter system. + */ + public suspend fun parse( + builder: () -> T, + context: SlashCommandContext<*, *> + ): T { val argumentsObj = builder.invoke() logger.debug { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } val args = argumentsObj.args.toMutableList() - val command = context.interaction.command + val command = context.event.interaction.command val values = command.options.mapValues { val option = it.value.value diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt new file mode 100644 index 0000000000..0dfd9c1c33 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt @@ -0,0 +1,39 @@ +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.InvalidCommandException +import mu.KLogger +import mu.KotlinLogging + +/** + * Slash command group, containing other slash commands. + * + * @param name Slash command group name + * @param parent Parent slash command that this group belongs to + */ +public class SlashGroup( + public val name: String, + public val parent: SlashCommand<*, *> +) { + internal val logger: KLogger = KotlinLogging.logger {} + + /** List of subcommands belonging to this group. **/ + public val subCommands: MutableList> = mutableListOf() + + /** Command group description, which is required and shown on Discord. **/ + public lateinit var description: String + + /** + * Validate this command group, ensuring it has everything it needs. + * + * Throws if not. + */ + public fun validate() { + if (!::description.isInitialized) { + throw InvalidCommandException(name, "No group description given.") + } + + if (subCommands.isEmpty()) { + error("Command groups must contain at least one subcommand.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt new file mode 100644 index 0000000000..83d4ccee38 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt @@ -0,0 +1,317 @@ +@file:Suppress("StringLiteralDuplication") + +package com.kotlindiscord.kord.extensions.commands.application.slash + +import com.kotlindiscord.kord.extensions.CommandRegistrationException +import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.commands.Arguments + +private const val SUBCOMMAND_AND_GROUP_LIMIT: Int = 25 + +// region: Group creation + +/** + * Create a command group, using the given name. + * + * Note that only root/top-level commands can contain command groups. An error will be thrown if you try to use + * this with a subcommand. + * + * @param name Name of the command group on Discord. + * @param body Lambda used to build the [SlashGroup] object. + */ +public suspend fun SlashCommand<*, *>.group(name: String, body: suspend SlashGroup.() -> Unit): SlashGroup { + if (parentCommand != null) { + error("Command groups may not be nested inside subcommands.") + } + + if (subCommands.isNotEmpty()) { + error("Commands may only contain subcommands or command groups, not both.") + } + + if (groups.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + error("Commands may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT command groups.") + } + + if (groups[name] != null) { + error("A command group with the name '$name' has already been registered.") + } + + val group = SlashGroup(name, this) + + body(group) + group.validate() + + groups[name] = group + + return group +} + +// endregion + +// region: Slash commands (Ephemeral) + +/** + * DSL function for easily registering an ephemeral subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +public suspend fun SlashCommand<*, *>.ephemeralSubCommand( + arguments: () -> T, + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(extension, arguments, parentCommand, parentGroup) + body(commandObj) + + return ephemeralSubCommand(commandObj) +} + +/** + * Function for registering a custom ephemeral slash command object, for subcommands. + * + * You can use this if you have a custom ephemeral slash command subclass you need to register. + * + * @param commandObj EphemeralSlashCommand object to register as a subcommand. + */ +public fun SlashCommand<*, *>.ephemeralSubCommand( + commandObj: EphemeralSlashCommand +): EphemeralSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering an ephemeral subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +public suspend fun SlashCommand<*, *>.ephemeralSubCommand( + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(extension, null, parentCommand, parentGroup) + body(commandObj) + + return ephemeralSubCommand(commandObj) +} + +// endregion + +// region: Slash commands (Public) + +/** + * DSL function for easily registering a public subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +public suspend fun SlashCommand<*, *>.publicSubCommand( + arguments: () -> T, + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(extension, arguments, parentCommand, parentGroup) + body(commandObj) + + return publicSubCommand(commandObj) +} + +/** + * Function for registering a custom public slash command object, for subcommands. + * + * You can use this if you have a custom public slash command subclass you need to register. + * + * @param commandObj PublicSlashCommand object to register as a subcommand. + */ +public fun SlashCommand<*, *>.publicSubCommand( + commandObj: PublicSlashCommand +): PublicSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering a public subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +public suspend fun SlashCommand<*, *>.publicSubCommand( + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(extension, null, parentCommand, parentGroup) + body(commandObj) + + return publicSubCommand(commandObj) +} + +// endregion + +// region: Slash groups (Ephemeral) + +/** + * DSL function for easily registering an ephemeral subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +public suspend fun SlashGroup.ephemeralSubCommand( + arguments: () -> T, + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(parent.extension, arguments, parent, this) + body(commandObj) + + return ephemeralSubCommand(commandObj) +} + +/** + * Function for registering a custom ephemeral slash command object, for subcommands. + * + * You can use this if you have a custom ephemeral slash command subclass you need to register. + * + * @param commandObj EphemeralSlashCommand object to register as a subcommand. + */ +public fun SlashGroup.ephemeralSubCommand( + commandObj: EphemeralSlashCommand +): EphemeralSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering an ephemeral subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +public suspend fun SlashGroup.ephemeralSubCommand( + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(parent.extension, null, parent, this) + body(commandObj) + + return ephemeralSubCommand(commandObj) +} + +// endregion + +// region: Slash groups (Public) + +/** + * DSL function for easily registering a public subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +public suspend fun SlashGroup.publicSubCommand( + arguments: () -> T, + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(parent.extension, arguments, parent, this) + body(commandObj) + + return publicSubCommand(commandObj) +} + +/** + * Function for registering a custom public slash command object, for subcommands. + * + * You can use this if you have a custom public slash command subclass you need to register. + * + * @param commandObj PublicSlashCommand object to register as a subcommand. + */ +public fun SlashGroup.publicSubCommand( + commandObj: PublicSlashCommand +): PublicSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering a public subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +public suspend fun SlashGroup.publicSubCommand( + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(parent.extension, null, parent, this) + body(commandObj) + + return publicSubCommand(commandObj) +} + +// endregion diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceConverter.kt similarity index 89% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceConverter.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceConverter.kt index 167ed4622a..a8df12a120 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceConverter.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.slash.converters +package com.kotlindiscord.kord.extensions.commands.application.slash.converters import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import dev.kord.common.annotation.KordPreview diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceEnum.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceEnum.kt similarity index 51% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceEnum.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceEnum.kt index d5568cd4fb..f84052d520 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/ChoiceEnum.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/ChoiceEnum.kt @@ -1,6 +1,6 @@ -package com.kotlindiscord.kord.extensions.commands.slash.converters +package com.kotlindiscord.kord.extensions.commands.application.slash.converters -import com.kotlindiscord.kord.extensions.commands.slash.converters.impl.EnumChoiceConverter +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.EnumChoiceConverter /** Interface representing an enum used in the [EnumChoiceConverter]. **/ public interface ChoiceEnum { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/EnumChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt similarity index 90% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/EnumChoiceConverter.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt index 5fb7a6c123..6a97d3b78a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/EnumChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt @@ -5,14 +5,14 @@ ConverterToOptional::class ) -package com.kotlindiscord.kord.extensions.commands.slash.converters.impl +package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceEnum import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.converters.ChoiceConverter -import com.kotlindiscord.kord.extensions.commands.slash.converters.ChoiceEnum import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt similarity index 91% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/NumberChoiceConverter.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt index 18d4f8a30f..a8a527f181 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/NumberChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt @@ -5,16 +5,16 @@ ConverterToOptional::class ) -package com.kotlindiscord.kord.extensions.commands.slash.converters.impl +package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter import com.kotlindiscord.kord.extensions.commands.converters.ConverterToDefaulting import com.kotlindiscord.kord.extensions.commands.converters.ConverterToMulti import com.kotlindiscord.kord.extensions.commands.converters.ConverterToOptional import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.slash.converters.ChoiceConverter import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/StringChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt similarity index 88% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/StringChoiceConverter.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt index ebe8707f4f..c7250f267d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/converters/impl/StringChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt @@ -5,15 +5,15 @@ ConverterToOptional::class ) -package com.kotlindiscord.kord.extensions.commands.slash.converters.impl +package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter import com.kotlindiscord.kord.extensions.commands.converters.ConverterToDefaulting import com.kotlindiscord.kord.extensions.commands.converters.ConverterToMulti import com.kotlindiscord.kord.extensions.commands.converters.ConverterToOptional import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.slash.converters.ChoiceConverter import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 752277a31f..101d7f43fa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -16,9 +16,14 @@ public typealias InitialEphemeralUserResponseBuilder = public class EphemeralUserCommand( extension: Extension ) : UserCommand(extension) { - /** Provide this tn open with a response, omit it to ack instead. **/ + /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialEphemeralUserResponseBuilder = null + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralUserResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: UserCommandInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 3bf0b0ab9d..dbad6f4a58 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -16,9 +16,14 @@ public typealias InitialPublicUserResponseBuilder = public class PublicUserCommand( extension: Extension ) : UserCommand(extension) { - /** Provide this tn open with a response, omit it to ack instead. **/ + /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialPublicUserResponseBuilder = null + /** Call this tn open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicUserResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: UserCommandInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 9bc25318ca..26753a00cf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -1,6 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType @@ -30,6 +31,14 @@ public abstract class UserCommand>( body = action } + override fun validate() { + super.validate() + + if (!::body.isInitialized) { + throw InvalidCommandException(name, "No command body given.") + } + } + /** Override this to implement your command's calling logic. Check subtypes for examples! **/ public abstract override suspend fun call(event: UserCommandInteractionCreateEvent) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt index 39148dd07a..741f89bae3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt @@ -12,7 +12,7 @@ import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent */ public abstract class UserCommandContext>( public open val event: UserCommandInteractionCreateEvent, - public open val command: UserCommand + public override val command: UserCommand ) : ApplicationCommandContext(event, command) { /** Messages that this message command is being executed against. **/ public val targetUsers: Collection = event.interaction.users?.values ?: listOf() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 0db4cde46d..17a5c6ab92 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -7,9 +7,8 @@ import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.Command -import com.kotlindiscord.kord.extensions.commands.parser.ArgumentParser -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.EMPTY_VALUE_STRING import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider @@ -54,7 +53,7 @@ public open class ChatCommand( public val translationsProvider: TranslationsProvider by inject() /** Message command registry. **/ - public val messageCommandRegistry: ChatCommandRegistry by inject() + public val registry: ChatCommandRegistry by inject() /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() @@ -121,8 +120,6 @@ public open class ChatCommand( */ public open val checkList: MutableList> = mutableListOf() - override val parser: ArgumentParser = ArgumentParser() - /** Permissions required to be able to run this command. **/ public open val requiredPerms: MutableSet = mutableSetOf() @@ -141,7 +138,7 @@ public open class ChatCommand( /** * Retrieve the command signature for a locale, which specifies how the command's arguments should be structured. * - * Command signatures are generated automatically by the [ArgumentParser]. + * Command signatures are generated automatically by the [ChatCommandParser]. */ public open suspend fun getSignature(locale: Locale): String { if (this.arguments == null) { @@ -156,7 +153,7 @@ public open class ChatCommand( locale ) } else { - signatureCache[locale] = parser.signature(arguments!!, locale) + signatureCache[locale] = registry.parser.signature(arguments!!, locale) } } @@ -442,7 +439,7 @@ public open class ChatCommand( } if (this.arguments != null) { - val parsedArgs = this.parser.parse(this.arguments!!, context) + val parsedArgs = registry.parser.parse(this.arguments!!, context) context.populateArgs(parsedArgs) } @@ -486,7 +483,7 @@ public open class ChatCommand( logger.error(t) { "Error during execution of $name command ($event)" } if (extension.bot.extensions.containsKey("sentry")) { - val prefix = messageCommandRegistry.getPrefix(event) + val prefix = registry.getPrefix(event) event.message.respond( context.translate( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index 7e05bce40c..2435dfee2b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator @@ -22,9 +22,10 @@ import dev.kord.rest.builder.message.create.MessageCreateBuilder import dev.kord.rest.builder.message.modify.MessageModifyBuilder /** - * Command context object representing the context given to message commands. + * Command context object representing the context given to chat commands. * - * @property messageCommand Message command object, typed as [ChatCommand] rather than [Command] + * @property messageCommand Chat command object + * @param parser String parser instance, if any - will be `null` if this isn't a chat command. * @property argString String containing the command's unparsed arguments, raw, fresh from Discord itself. */ @ExtensionDSL @@ -32,9 +33,9 @@ public open class ChatCommandContext( public val messageCommand: ChatCommand, eventObj: MessageCreateEvent, commandName: String, - parser: StringParser, + public open val parser: StringParser?, public val argString: String -) : CommandContext(messageCommand, eventObj, commandName, parser) { +) : CommandContext(messageCommand, eventObj, commandName) { /** Event that triggered this command execution. **/ public val event: MessageCreateEvent get() = eventObj as MessageCreateEvent @@ -73,9 +74,11 @@ public open class ChatCommandContext( override suspend fun getChannel(): MessageChannelBehavior = event.message.channel.asChannel() override suspend fun getGuild(): Guild? = event.getGuild() override suspend fun getMember(): Member? = event.message.getAuthorAsMember() - override suspend fun getMessage(): Message = event.message override suspend fun getUser(): User? = event.message.author + /** Extract message information from event data, if that context is available. **/ + public open suspend fun getMessage(): Message = event.message + /** * Convenience function to create a button paginator using a builder DSL syntax. Handles the contextual stuff for * you. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/ArgumentParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt similarity index 98% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/ArgumentParser.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt index 58108a0dcc..58652f1721 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/parser/ArgumentParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt @@ -5,10 +5,12 @@ "StringLiteralDuplication" ) -package com.kotlindiscord.kord.extensions.commands.parser +package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider @@ -34,7 +36,7 @@ private val logger = KotlinLogging.logger {} * * We recommend reading over the source code if you'd like to get to grips with how this all works. */ -public open class ArgumentParser : KoinComponent { +public open class ChatCommandParser : KoinComponent { /** Current instance of the bot. **/ public open val bot: ExtensibleBot by inject() @@ -63,7 +65,7 @@ public open class ArgumentParser : KoinComponent { * @return Built [Arguments] object, with converters filled. * @throws CommandException Thrown based on a lot of possible cases. This is intended for display on Discord. */ - public open suspend fun parse(builder: () -> T, context: CommandContext): T { + public open suspend fun parse(builder: () -> T, context: ChatCommandContext<*>): T { val argumentsObj = builder.invoke() val parser = context.parser!! diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt index 34114c2765..96838bd040 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt @@ -3,7 +3,7 @@ package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.utils.getLocale @@ -28,6 +28,9 @@ public open class ChatCommandRegistry : KoinComponent { /** Kord instance, backing the ExtensibleBot. **/ public val kord: Kord by inject() + /** Chat command parser object. **/ + public open val parser: ChatCommandParser = ChatCommandParser() + /** * A list of all registered commands. */ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index bec7e3251e..c0ef5fd0ab 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -4,7 +4,7 @@ import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.utils.getLocale diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt index ed918b1417..8ac1c92ca9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import java.util.* diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt index 96c4d99e49..14f4248e8a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.converters +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt index 41680134ce..07868ebd3c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.converters +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt index 7ad62c8dbf..b3066a83c6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt @@ -4,9 +4,9 @@ package com.kotlindiscord.kord.extensions.commands.converters import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt index f268922c3d..bdf48215aa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.converters +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt index 54b1d45b7b..449f58d1d1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt @@ -1,8 +1,8 @@ package com.kotlindiscord.kord.extensions.commands.converters import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt index 00205d02a1..39d261cbfc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.converters +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt index 2cd2947ecd..d7461e4ca4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.converters -import com.kotlindiscord.kord.extensions.commands.parser.Argument +import com.kotlindiscord.kord.extensions.commands.Argument import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/_Types.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/_Types.kt index 31df68305d..16507e5c52 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/_Types.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/_Types.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.converters +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Argument /** Types alias representing a validator callable. Keeps things relatively maintainable. **/ public typealias Validator = (suspend CommandContext.(arg: Argument<*>, value: T) -> Unit)? diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt index 425241f655..e0ba1b6532 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt @@ -1,9 +1,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt index 5397c4e20a..72df147a0b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt index 245ca13c21..c539509287 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt @@ -10,9 +10,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt index fcda12a897..f4da85e011 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt index a6dd90ca97..eab42cb30d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt @@ -1,10 +1,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.CoalescingConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt index 547aaee312..84468c30f1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt @@ -1,10 +1,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt index 03e3a64a03..e967684009 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt index 4900d0d082..46113fad10 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt index e8d486bfd5..f9b3661eb8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt @@ -7,9 +7,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt index 774954de16..1a57429687 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt index 14520b1938..d58bb1adf0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt index c10b5749cb..fa8646fccc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt index fd2008bfeb..47346f73a9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt @@ -8,9 +8,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser @@ -54,8 +55,8 @@ public class MemberConverter( override val signatureTypeString: String = "converters.member.signatureType" override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { - if (useReply) { - val messageReference = context.getMessage()?.asMessage()?.messageReference + if (useReply && context is ChatCommandContext<*>) { + val messageReference = context.message.asMessage().messageReference if (messageReference != null) { val member = messageReference.message?.asMessage()?.getAuthorAsMember() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt index 8fbc0c396e..c50f339373 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt @@ -8,9 +8,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser @@ -62,8 +63,8 @@ public class MessageConverter( override val signatureTypeString: String = "converters.message.signatureType" override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { - if (useReply) { - val messageReference = context.getMessage()?.asMessage()?.messageReference + if (useReply && context is ChatCommandContext<*>) { + val messageReference = context.message.asMessage().messageReference if (messageReference != null) { val message = messageReference.message?.asMessageOrNull() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt index 7eb6db5030..b341658a57 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt @@ -7,9 +7,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt index 31e8fd259c..c4ce6f7779 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt @@ -7,9 +7,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt index 114dee3507..a8bc5a68a3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt index bdabe5e602..e07d9c20b0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt @@ -8,9 +8,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt index 16820a50dc..25bab8fd70 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt @@ -7,9 +7,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt index 36415d85dc..eddfaad07e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt @@ -1,9 +1,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt index 7b2e87f5a2..28764b871b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt @@ -10,9 +10,9 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.i18n.SupportedLocales import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt index 333de39257..84b0ea5a83 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt @@ -8,10 +8,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt index 493a951bfa..79209db28d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt @@ -8,9 +8,10 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser @@ -49,8 +50,8 @@ public class UserConverter( override val signatureTypeString: String = "converters.user.signatureType" override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { - if (useReply) { - val messageReference = context.getMessage()?.asMessage()?.messageReference + if (useReply && context is ChatCommandContext<*>) { + val messageReference = context.message.asMessage().messageReference if (messageReference != null) { val user = messageReference.message?.asMessage()?.author?.asUserOrNull() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/Annotations.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/Annotations.kt deleted file mode 100644 index 1936db16fd..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/Annotations.kt +++ /dev/null @@ -1,13 +0,0 @@ -@file:Suppress("Filename", "MatchingDeclarationName") - -package com.kotlindiscord.kord.extensions.commands.slash - -@RequiresOptIn( - message = "Due to limitations in the Discord API, it's not currently possible to translate this property.", - - level = RequiresOptIn.Level.WARNING -) -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.PROPERTY) -/** Opt-in annotation to alert users that a string can't be translated yet. **/ -public annotation class TranslationNotSupported diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt deleted file mode 100644 index b5e9b0f093..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommand.kt +++ /dev/null @@ -1,726 +0,0 @@ -@file:OptIn(KordPreview::class, TranslationNotSupported::class) -@file:Suppress("StringLiteralDuplication") - -package com.kotlindiscord.kord.extensions.commands.slash - -import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.CommandRegistrationException -import com.kotlindiscord.kord.extensions.InvalidCommandException -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.checks.types.CheckContext -import com.kotlindiscord.kord.extensions.commands.Command -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.parser.SlashCommandParser -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType -import com.kotlindiscord.kord.extensions.sentry.SentryAdapter -import com.kotlindiscord.kord.extensions.sentry.tag -import com.kotlindiscord.kord.extensions.sentry.user -import com.kotlindiscord.kord.extensions.utils.getLocale -import com.kotlindiscord.kord.extensions.utils.permissionsForMember -import com.kotlindiscord.kord.extensions.utils.translate -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.Permission -import dev.kord.common.entity.Snowflake -import dev.kord.core.Kord -import dev.kord.core.KordObject -import dev.kord.core.any -import dev.kord.core.behavior.GuildBehavior -import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.respondPublic -import dev.kord.core.entity.channel.DmChannel -import dev.kord.core.entity.channel.GuildChannel -import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.core.entity.interaction.GroupCommand -import dev.kord.core.entity.interaction.SubCommand -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import io.sentry.Sentry -import kotlinx.coroutines.flow.toList -import mu.KLogger -import mu.KotlinLogging -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import java.util.* - -private val logger: KLogger = KotlinLogging.logger {} -private const val DISCORD_LIMIT: Int = 25 - -/** - * Class representing a slash command. - * - * You shouldn't need to use this class directly - instead, create an [Extension] and use the - * [slash command function][Extension.slashCommand] to register your command, by overriding the [Extension.setup] - * function. - * - * @param extension The [Extension] that registered this command. - * @param arguments Arguments object builder for this command, if it has arguments. - * @param parentCommand If this is a subcommand, the root command this command belongs to. - * @param parentGroup If this is a grouped subcommand, the group this command belongs to. - */ -@ExtensionDSL -public open class SlashCommand( - extension: Extension, - public open val arguments: (() -> T)? = null, - - public open val parentCommand: SlashCommand? = null, - public open val parentGroup: SlashGroup? = null -) : Command(extension), KoinComponent { - /** Translations provider, for retrieving translations. **/ - public val translationsProvider: TranslationsProvider by inject() - - private val settings: ExtensibleBotBuilder by inject() - - /** Kord instance, backing the ExtensibleBot. **/ - public val kord: Kord by inject() - - /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() - - /** Command description, as displayed on Discord. **/ - public open lateinit var description: String - - /** @suppress **/ - public open lateinit var body: suspend SlashCommandContext.() -> Unit - - /** Whether this command has a body/action set. **/ - public open val hasBody: Boolean get() = ::body.isInitialized - - /** Guild ID this slash command is to be registered for, if any. **/ - public open var guild: Snowflake? = if (parentCommand == null && parentGroup == null) { - settings.applicationCommandsBuilder.defaultGuild - } else { - null - } - - /** - * Whether to allow everyone to use this command by default. Set to `false` to use the allowed/disallowed role/user - * lists instead. This will be set to `false` automatically by the allow/disallow functions. - */ - public open var allowByDefault: Boolean = parentCommand?.allowByDefault ?: true - - /** - * List of allowed role IDs. Allows take priority over disallows. - */ - public open val allowedRoles: MutableSet = parentCommand?.allowedRoles ?: mutableSetOf() - - /** - * List of allowed invoker IDs. Allows take priority over disallows. - */ - public open val allowedUsers: MutableSet = parentCommand?.allowedUsers ?: mutableSetOf() - - /** - * List of disallowed role IDs. Allows take priority over disallows. - */ - public open val disallowedRoles: MutableSet = parentCommand?.disallowedRoles ?: mutableSetOf() - - /** - * List of disallowed invoker IDs. Allows take priority over disallows. - */ - public open val disallowedUsers: MutableSet = parentCommand?.disallowedUsers ?: mutableSetOf() - - /** Types of automatic ack to use, if any. **/ - public open var autoAck: AutoAckType = AutoAckType.EPHEMERAL - - /** Map of group names to slash command groups, if any. **/ - public open val groups: MutableMap = mutableMapOf() - - /** List of subcommands, if any. **/ - public open val subCommands: MutableList> = mutableListOf() - - /** @suppress **/ - public open val checkList: MutableList> = mutableListOf() - - public override val parser: SlashCommandParser = SlashCommandParser() - - /** Permissions required to be able to run this command. **/ - public open val requiredPerms: MutableSet = mutableSetOf() - - /** Translation cache, so we don't have to look up translations every time. **/ - public open val nameTranslationCache: MutableMap = mutableMapOf() - - /** Return this command's name translated for the given locale, cached as required. **/ - public open fun getTranslatedName(locale: Locale): String { - if (!nameTranslationCache.containsKey(locale)) { - nameTranslationCache[locale] = translationsProvider.translate( - this.name, - this.extension.bundle, - locale - ).lowercase() - } - - return nameTranslationCache[locale]!! - } - - /** - * An internal function used to ensure that all of a command's required properties are present. - * - * @throws InvalidCommandException Thrown when a required property hasn't been set. - */ - @Throws(InvalidCommandException::class) - public override fun validate() { - super.validate() - - if (!::description.isInitialized) { - throw InvalidCommandException(name, "No command description given.") - } - - if (!::body.isInitialized && groups.isEmpty() && subCommands.isEmpty()) { - throw InvalidCommandException(name, "No command action or subcommands/groups given.") - } - - if (::body.isInitialized && !(groups.isEmpty() && subCommands.isEmpty())) { - throw InvalidCommandException( - name, - - "Command action and subcommands/groups given, but slash commands may not have an action if they have" + - " a subcommand or group." - ) - } - - if (parentCommand != null && guild != null) { - throw InvalidCommandException( - name, - - "Subcommands may not be limited to specific guilds - set the `guild` property on the parent command " + - "instead." - ) - } - } - - /** If your bot requires permissions to be able to execute the command, add them using this function. **/ - public fun requirePermissions(vararg perms: Permission) { - perms.forEach { requiredPerms.add(it) } - } - - // region: DSL functions - - /** - * Create a command group, using the given name. - * - * Note that only root/top-level commands can contain command groups. An error will be thrown if you try to use - * this with a subcommand. - * - * @param name Name of the command group on Discord. - * @param body Lambda used to build the [SlashGroup] object. - */ - public open suspend fun group(name: String, body: suspend SlashGroup.() -> Unit): SlashGroup { - if (parentCommand != null) { - error("Command groups may not be nested inside subcommands.") - } - - if (subCommands.isNotEmpty()) { - error("Commands may only contain subcommands or command groups, not both.") - } - - if (groups.size >= DISCORD_LIMIT) { - error("Commands may only contain up to $DISCORD_LIMIT command groups.") - } - - if (groups[name] != null) { - error("A command group with the name '$name' has already been registered.") - } - - val group = SlashGroup(name, this) - - body.invoke(group) - group.validate() - - groups[name] = group - - return group - } - - /** Specify a specific guild for this slash command. **/ - public open fun guild(guild: Snowflake) { - this.guild = guild - } - - /** Specify a specific guild for this slash command. **/ - public open fun guild(guild: Long) { - this.guild = Snowflake(guild) - } - - /** Specify a specific guild for this slash command. **/ - public open fun guild(guild: GuildBehavior) { - this.guild = guild.id - } - - /** Register an allowed role, and set [allowByDefault] to `false`. **/ - public open fun allowRole(role: Snowflake) { - allowByDefault = false - - allowedRoles.add(role) - } - - /** Register an allowed role, and set [allowByDefault] to `false`. **/ - public open fun allowRole(role: UserBehavior): Unit = - allowRole(role.id) - - /** Register a disallowed role, and set [allowByDefault] to `false`. **/ - public open fun disallowRole(role: Snowflake) { - allowByDefault = false - - disallowedRoles.add(role) - } - - /** Register a disallowed role, and set [allowByDefault] to `false`. **/ - public open fun disallowRole(role: UserBehavior): Unit = - disallowRole(role.id) - - /** Register an allowed user, and set [allowByDefault] to `false`. **/ - public open fun allowUser(user: Snowflake) { - allowByDefault = false - - allowedUsers.add(user) - } - - /** Register an allowed user, and set [allowByDefault] to `false`. **/ - public open fun allowUser(user: UserBehavior): Unit = - allowUser(user.id) - - /** Register a disallowed user, and set [allowByDefault] to `false`. **/ - public open fun disallowUser(user: Snowflake) { - allowByDefault = false - - disallowedUsers.add(user) - } - - /** Register a disallowed user, and set [allowByDefault] to `false`. **/ - public open fun disallowUser(user: UserBehavior): Unit = - disallowUser(user.id) - - /** - * DSL function for easily registering a subcommand, with arguments. - * - * Use this in your setup function to register a subcommand that may be executed on Discord. - * - * @param arguments Arguments builder (probably a reference to the class constructor). - * @param body Builder lambda used for setting up the slash command object. - */ - public open suspend fun subCommand( - arguments: (() -> T), - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(this.extension, arguments, this) - body.invoke(commandObj) - - return subCommand(commandObj) - } - - /** - * DSL function for easily registering a subcommand, without arguments. - * - * Use this in your slash command function to register a subcommand that may be executed on Discord. - * - * @param body Builder lambda used for setting up the subcommand object. - */ - public open suspend fun subCommand( - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(this.extension, null, this) - body.invoke(commandObj) - - return subCommand(commandObj) - } - - /** - * Function for registering a custom slash command object, for subcommands. - * - * You can use this if you have a custom slash command subclass you need to register. - * - * @param commandObj SlashCommand object to register as a subcommand. - */ - public open suspend fun subCommand( - commandObj: SlashCommand - ): SlashCommand { - if (parentCommand != null) { - error("Subcommands may not be nested inside subcommands.") - } - - if (groups.isNotEmpty()) { - error("Commands may only contain subcommands or command groups, not both.") - } - - if (subCommands.size >= DISCORD_LIMIT) { - error("Commands may only contain up to $DISCORD_LIMIT top-level subcommands.") - } - - try { - commandObj.validate() - subCommands.add(commandObj) - } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register subcommand - $e" } - } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register subcommand - $e" } - } - - return commandObj - } - - /** - * Define what will happen when your command is invoked. - * - * @param action The body of your command, which will be executed when your command is invoked. - */ - public open fun action(action: suspend SlashCommandContext.() -> Unit) { - this.body = action - } - - /** - * Define a check which must pass for the command to be executed. - * - * A command may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to this command. - */ - public open fun check(vararg checks: Check) { - checks.forEach { checkList.add(it) } - } - - /** - * Overloaded check function to allow for DSL syntax. - * - * @param check Check to apply to this command. - */ - public open fun check(check: Check) { - checkList.add(check) - } - - /** - * Define a simple Boolean check which must pass for the command to be executed. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * A command may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to this command. - */ - public open fun booleanCheck(vararg checks: suspend (ChatInputCommandInteractionCreateEvent) -> Boolean) { - checks.forEach(::booleanCheck) - } - - /** - * Overloaded simple Boolean check function to allow for DSL syntax. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * @param check Check to apply to this command. - */ - public open fun booleanCheck(check: suspend (ChatInputCommandInteractionCreateEvent) -> Boolean) { - check { - if (check(event)) { - pass() - } else { - fail() - } - } - } - - // endregion - - /** Run checks with the provided [InteractionCreateEvent]. Return false if any failed, true otherwise. **/ - public open suspend fun runChecks( - event: ChatInputCommandInteractionCreateEvent, - sendMessage: Boolean = true - ): Boolean { - val locale = event.getLocale() - - // global checks - for (check in extension.bot.settings.applicationCommandsBuilder.slashCheckList) { - val context = CheckContext(event, locale) - - check(context) - - if (!context.passed) { - val message = context.message - - if (message != null && sendMessage) { - if (autoAck != AutoAckType.PUBLIC) { - event.interaction.respondEphemeral { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } else { - event.interaction.respondPublic { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } - } - - return false - } - } - - // local extension checks - for (check in extension.slashCommandChecks) { - val context = CheckContext(event, locale) - - check(context) - - if (!context.passed) { - val message = context.message - - if (message != null && sendMessage) { - if (autoAck == AutoAckType.EPHEMERAL) { - event.interaction.respondEphemeral { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } else { - event.interaction.respondPublic { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } - } - - return false - } - } - - // parent command checks - if (parentCommand != null) { - val parentChecks = parentCommand!!.runChecks(event) - - if (!parentChecks) { - return false - } - } - - // command-specific checks - for (check in checkList) { - val context = CheckContext(event, locale) - - check(context) - - if (!context.passed) { - val message = context.message - - if (message != null && sendMessage) { - if (autoAck == AutoAckType.EPHEMERAL) { - event.interaction.respondEphemeral { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } else { - event.interaction.respondPublic { - content = translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } - } - - return false - } - } - - val channel = event.interaction.channel.asChannel() as? GuildMessageChannel - - // check that discord should enforce, but we don't trust them to - if (!allowByDefault) { - if (channel != null) { - val member = event.interaction.user.asMember(channel.guildId) - - return member.id in allowedUsers || member.roles.any { it.id in allowedRoles } - } - } else { - if (channel != null) { - val member = event.interaction.user.asMember(channel.guildId) - - return member.id !in disallowedUsers && member.roles.toList().all { it.id !in disallowedRoles } - } - } - - return true - } - - /** - * Execute this command, given an [InteractionCreateEvent]. - * - * This function takes a [InteractionCreateEvent] (generated when a slash command is executed), and - * processes it. The command's checks are invoked and, assuming all of the - * checks passed, the [command body][action] is executed. - * - * If an exception is thrown by the [command body][action], it is caught and a traceback - * is printed. - * - * @param event The interaction creation event. - */ - public open suspend fun call(event: ChatInputCommandInteractionCreateEvent) { - val eventCommand = event.interaction.command - - // We lie to the compiler thrice below to work around an issue with generics. - val commandObj: SlashCommand = if (eventCommand is SubCommand) { - val firstSubCommandKey = eventCommand.name - - this.subCommands.firstOrNull { it.name == firstSubCommandKey } as SlashCommand? - ?: error("Unknown subcommand: $firstSubCommandKey") - } else if (eventCommand is GroupCommand) { - val firstEventGroupKey = eventCommand.groupName - val group = this.groups[firstEventGroupKey] ?: error("Unknown command group: $firstEventGroupKey") - val firstSubCommandKey = eventCommand.name - - group.subCommands.firstOrNull { it.name == firstSubCommandKey } as SlashCommand? - ?: error("Unknown subcommand: $firstSubCommandKey") - } else { - this as SlashCommand - } - - if (!commandObj.runChecks(event)) { - return - } - - val resp = when (commandObj.autoAck) { - AutoAckType.EPHEMERAL -> event.interaction.acknowledgeEphemeral() - AutoAckType.PUBLIC -> event.interaction.acknowledgePublic() - - AutoAckType.NONE -> null - } - - val context = SlashCommandContext(commandObj, event, commandObj.name, resp) - - context.populate() - - if (sentry.enabled) { - context.sentry.breadcrumb(BreadcrumbType.User) { - category = "command.slash" - message = "Slash command \"${commandObj.name}\" called." - - val channel = context.channel.asChannelOrNull() - val guild = context.guild?.asGuildOrNull() - - data["command"] = commandObj.name - - if (this@SlashCommand.guild != null) { - data["command.guild"] to this@SlashCommand.guild!!.asString - } - - if (channel != null) { - data["channel"] = when (channel) { - is DmChannel -> "Private Message (${channel.id.asString})" - is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" - - else -> channel.id.asString - } - } - - if (guild != null) { - data["guild"] = "${guild.name} (${guild.id.asString})" - } - } - } - - @Suppress("TooGenericExceptionCaught") - try { - if (context.guild != null) { - val perms = (context.channel.asChannel() as GuildChannel) - .permissionsForMember(kord.selfId) - - val missingPerms = requiredPerms.filter { !perms.contains(it) } - - if (missingPerms.isNotEmpty()) { - throw CommandException( - context.translate( - "commands.error.missingBotPermissions", - null, - replacements = arrayOf( - missingPerms.map { it.translate(context) }.joinToString(", ") - ) - ) - ) - } - } - - if (commandObj.arguments != null) { - val args = commandObj.parser.parse(commandObj.arguments!!, context) - context.populateArgs(args) - } - - commandObj.body(context) - } catch (e: CommandException) { - respondText(context, e.reason) - } catch (t: Throwable) { - if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } - - val channel = context.channel - val author = context.user.asUserOrNull() - - val sentryId = context.sentry.captureException(t, "Slash command execution failed.") { - if (author != null) { - user(author) - } - - tag("private", "false") - - if (channel is DmChannel) { - tag("private", "true") - } - - tag("command", commandObj.name) - tag("extension", commandObj.extension.name) - - Sentry.captureException(t, "SlashCommand execution failed.") - } - - logger.debug { "Error submitted to Sentry: $sentryId" } - - logger.error(t) { "Error during execution of ${commandObj.name} slash command ($event)" } - - val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { - context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) - } else { - context.translate("commands.error.user", null) - } - - respondText(context, errorMessage) - } else { - logger.error(t) { "Error during execution of ${commandObj.name} slash command ($event)" } - - respondText(context, context.translate("commands.error.user", null)) - } - } - } - - private suspend fun respondText( - context: SlashCommandContext<*>, - text: String - ): KordObject = when (context.isEphemeral) { - null -> { - context.ack(true) - context.ephemeralFollowUp { content = text } - } - - true -> context.ephemeralFollowUp { content = text } - false -> context.publicFollowUp { content = text } - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt deleted file mode 100644 index 56e1b41b4f..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandContext.kt +++ /dev/null @@ -1,214 +0,0 @@ -package com.kotlindiscord.kord.extensions.commands.slash - -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.checks.channelFor -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.memberFor -import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.pagination.InteractionButtonPaginator -import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.MemberBehavior -import dev.kord.core.behavior.MessageBehavior -import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.Guild -import dev.kord.core.entity.channel.MessageChannel -import dev.kord.core.entity.interaction.CommandInteraction -import dev.kord.core.entity.interaction.InteractionFollowup -import dev.kord.core.entity.interaction.PublicFollowupMessage -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.MessageCreateBuilder -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.modify.MessageModifyBuilder - -/** - * Command context object representing the context given to message commands. - * - * @property interactionResponse Interaction response object, for following up - */ -@OptIn(KordPreview::class) -@ExtensionDSL -public open class SlashCommandContext( - private val slashCommand: SlashCommand, - event: ChatInputCommandInteractionCreateEvent, - commandName: String, - public var interactionResponse: InteractionResponseBehavior? = null -) : CommandContext(slashCommand, event, commandName, null) { - /** Event that triggered this command execution. **/ - public val event: ChatInputCommandInteractionCreateEvent get() = eventObj as ChatInputCommandInteractionCreateEvent - - /** Quick access to the [CommandInteraction]. **/ - public val interaction: CommandInteraction get() = event.interaction as CommandInteraction - - /** Channel this command happened in. **/ - public open lateinit var channel: MessageChannel - - /** Guild this command happened in. **/ - public open var guild: Guild? = null - - /** Guild member responsible for executing this command. **/ - public open var member: MemberBehavior? = null - - /** User responsible for executing this command. **/ - public open lateinit var user: UserBehavior - - /** Arguments object containing this command's parsed arguments. **/ - public open lateinit var arguments: T - - /** Whether a response or ack has already been sent by the user. **/ - public open val acked: Boolean get() = interactionResponse != null - - /** Whether we're working ephemerally, or null if no ack or response was sent yet. **/ - public open val isEphemeral: Boolean? - get() = when (interactionResponse) { - is EphemeralInteractionResponseBehavior -> true - is PublicInteractionResponseBehavior -> false - - else -> null - } - - override val command: SlashCommand get() = slashCommand - - override suspend fun populate() { - channel = getChannel() - guild = getGuild() - member = getMember() - user = getUser() - } - - /** @suppress Internal function **/ - public fun populateArgs(args: T) { - arguments = args - } - - override suspend fun getChannel(): MessageChannel = channelFor(event)!!.asChannel() as MessageChannel - override suspend fun getGuild(): Guild? = guildFor(event)?.asGuildOrNull() - override suspend fun getMember(): MemberBehavior? = memberFor(event)?.asMemberOrNull() - override suspend fun getMessage(): MessageBehavior? = null - override suspend fun getUser(): UserBehavior = event.interaction.user - - /** - * Convenience function to create a button paginator using a builder DSL syntax. Handles the contextual stuff for - * you. - */ - public suspend fun paginator( - defaultGroup: String = "", - body: suspend PaginatorBuilder.() -> Unit - ): InteractionButtonPaginator { - val builder = PaginatorBuilder(command.extension, getLocale(), defaultGroup = defaultGroup) - - body(builder) - - return InteractionButtonPaginator(builder, this) - } - - /** - * Send an acknowledgement manually, assuming you have `autoAck` set to `NONE`. - * - * Note that what you supply for `ephemeral` will decide how the rest of your interactions - both responses and - * follow-ups. They must match in ephemeral state. - * - * This function will throw an exception if an acknowledgement or response has already been sent. - * - * @param ephemeral Whether this should be an ephemeral acknowledgement or not. - */ - public suspend fun ack(ephemeral: Boolean): InteractionResponseBehavior { - if (acked) { - error("Attempted to acknowledge an interaction that's already been acknowledged.") - } - - interactionResponse = if (ephemeral) { - event.interaction.acknowledgeEphemeral() - } else { - event.interaction.acknowledgePublic() - } - - return interactionResponse!! - } - - /** - * Assuming an acknowledgement or response has been sent, send an ephemeral follow-up message. - * - * This function will throw an exception if no acknowledgement or response has been sent yet, or this interaction - * has already been interacted with in a non-ephemeral manner. - * - * Note that ephemeral follow-ups require a content string, and may not contain embeds or files. - */ - public suspend inline fun ephemeralFollowUp( - builder: EphemeralFollowupMessageCreateBuilder.() -> Unit = {} - ): InteractionFollowup { - if (interactionResponse == null) { - error("Tried send an interaction follow-up before acknowledging it.") - } - - if (isEphemeral == false) { - error("Tried send an ephemeral follow-up for a public interaction.") - } - - return (interactionResponse as EphemeralInteractionResponseBehavior).followUpEphemeral(builder) - } - - /** - * Assuming an acknowledgement or response has been sent, send a public follow-up message. - * - * This function will throw an exception if no acknowledgement or response has been sent yet, or this interaction - * has already been interacted with in an ephemeral manner. - */ - public suspend inline fun publicFollowUp( - builder: PublicFollowupMessageCreateBuilder.() -> Unit - ): PublicFollowupMessage { - if (interactionResponse == null) { - error("Tried send an interaction follow-up before acknowledging it.") - } - - if (isEphemeral == true) { - error("Tried to send a public follow-up for an ephemeral interaction.") - } - - return (interactionResponse as PublicInteractionResponseBehavior).followUp(builder) - } - - /** - * Convenience function for adding components to your message via the [Components] class. - * - * @see Components - */ - public suspend fun MessageCreateBuilder.components( - timeoutSeconds: Long? = null, - body: suspend Components.() -> Unit - ): Components { - val components = Components(command.extension, this@SlashCommandContext) - - body(components) - - with(components) { - setup(timeoutSeconds) - } - - return components - } - - /** - * Convenience function for adding components to your message via the [Components] class. - * - * @see Components - */ - public suspend fun MessageModifyBuilder.components( - timeoutSeconds: Long? = null, - body: suspend Components.() -> Unit - ): Components { - val components = Components(command.extension, this@SlashCommandContext) - - body(components) - - with(components) { - setup(timeoutSeconds) - } - - return components - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt index b5b6074c07..81005c5f3b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt @@ -1,12 +1,10 @@ -@file:OptIn(TranslationNotSupported::class) - @file:Suppress("StringLiteralDuplication") package com.kotlindiscord.kord.extensions.commands.slash import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.converters.SlashCommandConverter -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake @@ -42,18 +40,18 @@ public open class SlashCommandRegistry : KoinComponent { public val translationsProvider: TranslationsProvider by inject() /** @suppress **/ - public open val commands: MutableMap>> = mutableMapOf( + public open val commands: MutableMap>> = mutableMapOf( null to mutableListOf() // So that global commands always have a list here ) /** @suppress **/ - public open val commandMap: MutableMap> = mutableMapOf() + public open val commandMap: MutableMap> = mutableMapOf() // TODO: Sentry? // private val sentry: SentryAdapter by bot.koin.inject() /** Register a slash command here, before they're synced to Discord. **/ - public open fun register(command: SlashCommand, guild: Snowflake? = null): Boolean { + public open fun register(command: SlashCommand<*, *>, guild: Snowflake? = null): Boolean { val locale = bot.settings.i18nBuilder.defaultLocale commands.putIfAbsent(guild, mutableListOf()) @@ -192,7 +190,7 @@ public open class SlashCommandRegistry : KoinComponent { it.delete() } } else { - toCreate.groupBy { it.guild!! }.forEach { (snowflake, commands) -> + toCreate.groupBy { it.guildId!! }.forEach { (snowflake, commands) -> val response = kord.createGuildApplicationCommands(snowflake) { commands.forEach { val translatedName = it.getTranslatedName(locale) @@ -220,7 +218,7 @@ public open class SlashCommandRegistry : KoinComponent { } val commandsWithPerms = commandMap.filterValues { !it.allowByDefault }.toList().groupBy { - it.second.guild + it.second.guildId } commandsWithPerms.forEach { (guild, commands) -> @@ -251,10 +249,10 @@ public open class SlashCommandRegistry : KoinComponent { } } - internal open suspend fun ChatInputCreateBuilder.register(command: SlashCommand) { + internal open suspend fun ChatInputCreateBuilder.register(command: SlashCommand<*, *>) { val locale = bot.settings.i18nBuilder.defaultLocale - this.defaultPermission = command.guild == null || command.allowByDefault + this.defaultPermission = command.guildId == null || command.allowByDefault if (command.hasBody) { val args = command.arguments?.invoke() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashGroup.kt deleted file mode 100644 index 41c66e551e..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashGroup.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.kotlindiscord.kord.extensions.commands.slash - -import com.kotlindiscord.kord.extensions.CommandRegistrationException -import com.kotlindiscord.kord.extensions.InvalidCommandException -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import mu.KLogger -import mu.KotlinLogging - -private val logger: KLogger = KotlinLogging.logger {} -private const val DISCORD_LIMIT: Int = 10 - -/** - * Object representing a set of grouped slash commands. - * - * @param name Name of this command group, shown on Discord. - * @param parent Root/top-level command that owns this group. - */ -@ExtensionDSL -public open class SlashGroup( - public val name: String, - public val parent: SlashCommand -) { - /** List of subcommands belonging to this group. **/ - public val subCommands: MutableList> = mutableListOf() - - /** Command group description, which is required and shown on Discord. **/ - public lateinit var description: String - - /** - * Validate this command group, ensuring it has everything it needs. - * - * Throws if not. - */ - public open fun validate() { - if (!::description.isInitialized) { - throw InvalidCommandException(name, "No group description given.") - } - - if (subCommands.isEmpty()) { - error("Command groups must contain at least one subcommand.") - } - } - - /** - * DSL function for easily registering a grouped subcommand, with arguments. - * - * Use this in your group function to register a grouped subcommand that may be executed on Discord. - * - * @param arguments Arguments builder (probably a reference to the class constructor). - * @param body Builder lambda used for setting up the subcommand object. - */ - public open suspend fun subCommand( - arguments: (() -> T), - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(parent.extension, arguments, parent, this) - body.invoke(commandObj) - - return subCommand(commandObj) - } - - /** - * DSL function for easily registering a grouped subcommand, without arguments. - * - * Use this in your group function to register a grouped subcommand that may be executed on Discord. - * - * @param body Builder lambda used for setting up the subcommand object. - */ - public open suspend fun subCommand( - body: suspend SlashCommand.() -> Unit - ): SlashCommand { - val commandObj = SlashCommand(parent.extension, null, parent, this) - body.invoke(commandObj) - - return subCommand(commandObj) - } - - /** - * Function for registering a grouped custom slash command object, for subcommands. - * - * You can use this if you have a custom slash command subclass you need to register. - * - * @param commandObj SlashCommand object to register as a grouped subcommand. - */ - public open suspend fun subCommand( - commandObj: SlashCommand - ): SlashCommand { - if (subCommands.size >= DISCORD_LIMIT) { - error("Groups may only contain up to 10 subcommands.") - } - - try { - commandObj.validate() - subCommands.add(commandObj) - } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register subcommand - $e" } - } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register subcommand - $e" } - } - - return commandObj - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/AutoAckType.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt similarity index 84% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/AutoAckType.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt index ce346c1d49..4de39915c7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/AutoAckType.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.commands.slash +package com.kotlindiscord.kord.extensions.components /** Acknowledgement type for autoAck. **/ public sealed class AutoAckType { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt index 54055ed785..4366a8775e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt @@ -2,8 +2,7 @@ package com.kotlindiscord.kord.extensions.components -import com.kotlindiscord.kord.extensions.commands.slash.AutoAckType -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.components.builders.* import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.extensions.Extension @@ -42,7 +41,7 @@ private const val COMPONENTS_PER_ROW = 5 */ public open class Components( public open val extension: Extension, - public open val parentContext: SlashCommandContext<*>? = null + public open val parentContext: SlashCommandContext<*, *>? = null ) : KoinComponent { private val logger = KotlinLogging.logger {} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt index 9808d53545..367ec8ffb3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt @@ -6,8 +6,9 @@ import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.checks.* import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext -import com.kotlindiscord.kord.extensions.commands.slash.AutoAckType -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.components.AutoAckType import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.components.contexts.ActionableComponentContext import com.kotlindiscord.kord.extensions.extensions.Extension @@ -157,7 +158,7 @@ public abstract class ActionableComponentBuilder? + parentContext: SlashCommandContext<*, *>? ) { if (!runChecks(event)) { return @@ -192,7 +193,7 @@ public abstract class ActionableComponentBuilder) { true -> interaction.ackEphemeral(deferredAck) false -> interaction.ackPublic(deferredAck) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt index 72850db07a..666ed4c6a2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt @@ -3,7 +3,7 @@ package com.kotlindiscord.kord.extensions.components.builders import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.SentryAdapter @@ -44,7 +44,7 @@ public abstract class ComponentBuilder : KoinComponent { components: Components, extension: Extension, event: ComponentInteractionCreateEvent, - parentContext: SlashCommandContext<*>? = null + parentContext: SlashCommandContext<*, *>? = null ) { throw UnsupportedOperationException("This type of component doesn't support callable actions.") } 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 b9a6addb6d..b3133eae4a 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 @@ -5,10 +5,12 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.commands.Arguments +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 import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.events.ExtensionStateEvent @@ -80,7 +82,23 @@ public abstract class Extension : KoinComponent { * Unlike normal commands, slash commands cannot be unregistered dynamically. However, slash commands that * belong to unloaded extensions will not execute. */ - public open val slashCommands: MutableList> = mutableListOf() + public open val messageCommands: MutableList> = mutableListOf() + + /** + * List of registered slash commands. + * + * Unlike normal commands, slash commands cannot be unregistered dynamically. However, slash commands that + * belong to unloaded extensions will not execute. + */ + public open val slashCommands: MutableList> = mutableListOf() + + /** + * List of registered slash commands. + * + * Unlike normal commands, slash commands cannot be unregistered dynamically. However, slash commands that + * belong to unloaded extensions will not execute. + */ + public open val userCommands: MutableList> = mutableListOf() /** * List of message 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 651ec8d8d9..db3884bcb0 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 @@ -1,128 +1,310 @@ +@file:Suppress("StringLiteralDuplication") + package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.commands.Arguments +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 +import com.kotlindiscord.kord.extensions.commands.application.slash.PublicSlashCommand +import com.kotlindiscord.kord.extensions.commands.application.user.EphemeralUserCommand +import com.kotlindiscord.kord.extensions.commands.application.user.PublicUserCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommand import mu.KotlinLogging private val logger = KotlinLogging.logger {} +// region: Message commands + +/** Register an ephemeral message command, DSL-style. **/ +public suspend fun Extension.ephemeralMessageCommand( + body: suspend EphemeralMessageCommand.() -> Unit +): EphemeralMessageCommand { + val commandObj = EphemeralMessageCommand(this) + body(commandObj) + + return ephemeralMessageCommand(commandObj) +} + +/** Register a custom instance of an ephemeral message command. **/ +public fun Extension.ephemeralMessageCommand( + commandObj: EphemeralMessageCommand +): EphemeralMessageCommand { + try { + commandObj.validate() + messageCommands.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" } + } + + return commandObj +} + +/** Register a public message command, DSL-style. **/ +public suspend fun Extension.publicMessageCommand( + body: suspend PublicMessageCommand.() -> Unit +): PublicMessageCommand { + val commandObj = PublicMessageCommand(this) + body(commandObj) + + return publicMessageCommand(commandObj) +} + +/** Register a custom instance of a public message command. **/ +public fun Extension.publicMessageCommand( + commandObj: PublicMessageCommand +): PublicMessageCommand { + try { + commandObj.validate() + messageCommands.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" } + } + + return commandObj +} + +// endregion + +// region: Slash commands (Ephemeral) + /** - * DSL function for easily registering a command. + * DSL function for easily registering an ephemeral slash command, with arguments. * - * Use this in your setup function to register a command that may be executed on Discord. + * Use this in your setup function to register a slash command that may be executed on Discord. * - * @param body Builder lambda used for setting up the command object. + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. */ -@ExtensionDSL -public suspend fun Extension.chatCommand( +public suspend fun Extension.ephemeralSlashCommand( arguments: () -> T, - body: suspend ChatCommand.() -> Unit -): ChatCommand { - val commandObj = ChatCommand(this, arguments) - body.invoke(commandObj) + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(this, arguments, null, null) + body(commandObj) - return chatCommand(commandObj) + return ephemeralSlashCommand(commandObj) } /** - * DSL function for easily registering a command, without arguments. + * Function for registering a custom ephemeral slash command object. * - * Use this in your setup function to register a command that may be executed on Discord. + * You can use this if you have a custom ephemeral slash command subclass you need to register. * - * @param body Builder lambda used for setting up the command object. + * @param commandObj EphemeralSlashCommand object to register. */ -@ExtensionDSL -public suspend fun Extension.chatCommand( - body: suspend ChatCommand.() -> Unit -): ChatCommand { - val commandObj = ChatCommand(this) - body.invoke(commandObj) +public fun Extension.ephemeralSlashCommand( + commandObj: EphemeralSlashCommand +): EphemeralSlashCommand { + try { + commandObj.validate() + slashCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } - return chatCommand(commandObj) + return commandObj } /** - * Function for registering a custom command object. + * DSL function for easily registering an ephemeral slash command, without arguments. * - * You can use this if you have a custom command subclass you need to register. + * Use this in your setup function to register a slash command that may be executed on Discord. * - * @param commandObj MessageContentCommand object to register. + * @param body Builder lambda used for setting up the slash command object. */ -public fun Extension.chatCommand( - commandObj: ChatCommand -): ChatCommand { +public suspend fun Extension.ephemeralSlashCommand( + body: suspend EphemeralSlashCommand.() -> Unit +): EphemeralSlashCommand { + val commandObj = EphemeralSlashCommand(this, null, null, null) + body(commandObj) + + return ephemeralSlashCommand(commandObj) +} + +// endregion + +// region: Slash commands (Public) + +/** + * DSL function for easily registering a public slash command, with arguments. + * + * Use this in your setup function to register a slash command that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +public suspend fun Extension.publicSlashCommand( + arguments: () -> T, + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(this, arguments, null, null) + body(commandObj) + + return publicSlashCommand(commandObj) +} + +/** + * Function for registering a custom public slash command object. + * + * You can use this if you have a custom public slash command subclass you need to register. + * + * @param commandObj PublicSlashCommand object to register. + */ +public fun Extension.publicSlashCommand( + commandObj: PublicSlashCommand +): PublicSlashCommand { try { commandObj.validate() - chatCommandRegistry.add(commandObj) - chatCommands.add(commandObj) + slashCommands.add(commandObj) } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register command - $e" } + logger.error(e) { "Failed to register subcommand - $e" } } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register command - $e" } + logger.error(e) { "Failed to register subcommand - $e" } } return commandObj } /** - * DSL function for easily registering a slash command, with arguments. + * DSL function for easily registering a public slash command, without arguments. * * Use this in your setup function to register a slash command that may be executed on Discord. * - * @param arguments Arguments builder (probably a reference to the class constructor). * @param body Builder lambda used for setting up the slash command object. */ +public suspend fun Extension.publicSlashCommand( + body: suspend PublicSlashCommand.() -> Unit +): PublicSlashCommand { + val commandObj = PublicSlashCommand(this, null, null, null) + body(commandObj) + + return publicSlashCommand(commandObj) +} + +// endregion + +// region: User commands + +/** Register an ephemeral user command, DSL-style. **/ +public suspend fun Extension.ephemeralUserCommand( + body: suspend EphemeralUserCommand.() -> Unit +): EphemeralUserCommand { + val commandObj = EphemeralUserCommand(this) + body(commandObj) + + return ephemeralUserCommand(commandObj) +} + +/** Register a custom instance of an ephemeral user command. **/ +public fun Extension.ephemeralUserCommand( + commandObj: EphemeralUserCommand +): EphemeralUserCommand { + try { + commandObj.validate() + userCommands.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" } + } + + return commandObj +} + +/** Register a public user command, DSL-style. **/ +public suspend fun Extension.publicUserCommand( + body: suspend PublicUserCommand.() -> Unit +): PublicUserCommand { + val commandObj = PublicUserCommand(this) + body(commandObj) + + return publicUserCommand(commandObj) +} + +/** Register a custom instance of a public user command. **/ +public fun Extension.publicUserCommand( + commandObj: PublicUserCommand +): PublicUserCommand { + try { + commandObj.validate() + userCommands.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" } + } + + return commandObj +} + +// endregion + +// region: Chat commands + +/** + * DSL function for easily registering a command. + * + * Use this in your setup function to register a command that may be executed on Discord. + * + * @param body Builder lambda used for setting up the command object. + */ @ExtensionDSL -public suspend fun Extension.slashCommand( +public suspend fun Extension.chatCommand( arguments: () -> T, - body: suspend SlashCommand.() -> Unit -): SlashCommand { - val commandObj = SlashCommand(this, arguments) + body: suspend ChatCommand.() -> Unit +): ChatCommand { + val commandObj = ChatCommand(this, arguments) body.invoke(commandObj) - return slashCommand(commandObj) + return chatCommand(commandObj) } /** - * DSL function for easily registering a slash command, without arguments. + * DSL function for easily registering a command, without arguments. * - * Use this in your setup function to register a slash command that may be executed on Discord. + * Use this in your setup function to register a command that may be executed on Discord. * - * @param body Builder lambda used for setting up the slash command object. + * @param body Builder lambda used for setting up the command object. */ @ExtensionDSL -public suspend fun Extension.slashCommand( - body: suspend SlashCommand.() -> Unit -): SlashCommand { - val commandObj = SlashCommand(this, null) +public suspend fun Extension.chatCommand( + body: suspend ChatCommand.() -> Unit +): ChatCommand { + val commandObj = ChatCommand(this) body.invoke(commandObj) - return slashCommand(commandObj) + return chatCommand(commandObj) } /** - * Function for registering a custom slash command object. + * Function for registering a custom command object. * - * You can use this if you have a custom slash command subclass you need to register. + * You can use this if you have a custom command subclass you need to register. * - * @param commandObj SlashCommand object to register. + * @param commandObj MessageContentCommand object to register. */ -public fun Extension.slashCommand( - commandObj: SlashCommand -): SlashCommand { +public fun Extension.chatCommand( + commandObj: ChatCommand +): ChatCommand { try { commandObj.validate() - slashCommands.add(commandObj) - slashCommandsRegistry.register(commandObj, commandObj.guild) + chatCommandRegistry.add(commandObj) + chatCommands.add(commandObj) } catch (e: CommandRegistrationException) { - logger.error(e) { "Failed to register slash command - $e" } + logger.error(e) { "Failed to register command - $e" } } catch (e: InvalidCommandException) { - logger.error(e) { "Failed to register slash command - $e" } + logger.error(e) { "Failed to register command - $e" } } return commandObj @@ -168,3 +350,5 @@ public suspend fun Extension.chatGroupCommand( return chatCommand(commandObj) as ChatGroupCommand } + +// endregion diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt index a91fbb7c39..be39f1ccaa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/base/HelpProvider.kt @@ -1,9 +1,9 @@ package com.kotlindiscord.kord.extensions.extensions.base +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.pagination.BasePaginator import com.kotlindiscord.kord.extensions.utils.getKoin import dev.kord.core.event.message.MessageCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt index 681c4719a6..3e72af5db8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt @@ -3,9 +3,9 @@ package com.kotlindiscord.kord.extensions.extensions.impl import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.* import com.kotlindiscord.kord.extensions.commands.converters.impl.stringList -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider import com.kotlindiscord.kord.extensions.extensions.chatCommand diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index ea8c9cb840..66fa311cf6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -1,15 +1,15 @@ -@file:OptIn(KordPreview::class, TranslationNotSupported::class) +@file:OptIn(KordPreview::class) package com.kotlindiscord.kord.extensions.extensions.impl import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescedString import com.kotlindiscord.kord.extensions.commands.converters.impl.string -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand -import com.kotlindiscord.kord.extensions.extensions.slashCommand +import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.sentryId import com.kotlindiscord.kord.extensions.utils.respond @@ -40,13 +40,13 @@ public class SentryExtension : Extension() { @Suppress("StringLiteralDuplication") // It's the command name override suspend fun setup() { if (sentryAdapter.enabled) { - slashCommand(::FeedbackSlashArgs) { + ephemeralSlashCommand(::FeedbackSlashArgs) { name = "extensions.sentry.commandName" description = "extensions.sentry.commandDescription.short" action { if (!sentryAdapter.hasEventId(arguments.id)) { - ephemeralFollowUp { + respond { content = translate("extensions.sentry.error.invalidId") } @@ -63,7 +63,7 @@ public class SentryExtension : Extension() { Sentry.captureUserFeedback(feedback) sentryAdapter.removeEventId(arguments.id) - ephemeralFollowUp { + respond { content = translate("extensions.sentry.thanks") } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt index 1244c295fb..2dbaa4ff30 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.pagination -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder @@ -32,10 +33,10 @@ public class InteractionButtonPaginator( bundle: String? = null, locale: Locale? = null, - public val parentContext: SlashCommandContext<*>, + public val parentContext: SlashCommandContext<*, *>, ) : BaseButtonPaginator(extension, pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { init { - if (parentContext.isEphemeral == true) { + if (parentContext is EphemeralSlashCommandContext<*>) { error("Paginators cannot operate with ephemeral interactions.") } } @@ -51,13 +52,15 @@ public class InteractionButtonPaginator( if (embedInteraction == null) { setup() - embedInteraction = parentContext.publicFollowUp { - embed { applyPage() } + TODO() - with(this@InteractionButtonPaginator.components) { - this@publicFollowUp.setup(timeoutSeconds) - } - } +// embedInteraction = parentContext.respond { +// embed { applyPage() } +// +// with(this@InteractionButtonPaginator.components) { +// this@publicFollowUp.setup(timeoutSeconds) +// } +// } } else { updateButtons() @@ -97,7 +100,7 @@ public class InteractionButtonPaginator( @Suppress("FunctionNaming") // Factory function public fun InteractionButtonPaginator( builder: PaginatorBuilder, - parentContext: SlashCommandContext<*> + parentContext: SlashCommandContext<*, *> ): InteractionButtonPaginator = InteractionButtonPaginator( extension = builder.extension, pages = builder.pages, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/Paginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/Paginator.kt deleted file mode 100644 index d4ddcba78a..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/Paginator.kt +++ /dev/null @@ -1,352 +0,0 @@ -package com.kotlindiscord.kord.extensions.pagination - -import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.pagination.pages.Page -import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import com.kotlindiscord.kord.extensions.utils.respond -import com.kotlindiscord.kord.extensions.utils.waitFor -import dev.kord.common.annotation.KordPreview -import dev.kord.core.Kord -import dev.kord.core.behavior.MessageBehavior -import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.behavior.channel.createEmbed -import dev.kord.core.behavior.edit -import dev.kord.core.entity.Message -import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User -import dev.kord.core.entity.channel.DmChannel -import dev.kord.core.event.Event -import dev.kord.core.event.message.ReactionAddEvent -import dev.kord.core.event.message.ReactionRemoveEvent -import dev.kord.rest.builder.message.create.embed -import dev.kord.rest.builder.message.modify.embed -import kotlinx.coroutines.delay -import mu.KotlinLogging -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import java.util.* - -private const val WRONG_TYPE = "Wrong event type!" - -private val logger = KotlinLogging.logger {} - -/** - * Paginator for, well, pagination. - * - * This paginator is fairly extensible, supporting subclassing of this class, as well as the [Pages] and [Page] - * classes. It's designed with page groups in mind, which means you can provide groups of pages that can be switched - * between using a dedicated reaction. - * - * @param bot The bot object this paginator was created for - * @param targetChannel The channel this paginator should be created within - * @param targetMessage The message this paginator should be created in response to - * @param pages Set of pages this paginator should paginate - * @param pingInReply When [targetMessage] is provided, whether to ping the message author in the reply - * @param owner Optional paginator owner, if you want to prevent other users from using the reactions - * @param timeout Optional timeout, after which the paginator will be destroyed - * @param keepEmbed Whether to keep the embed after the paginator is destroyed, `false` by default - * @param switchEmoji If you have multiple groups, this is the emoji used to switch between them - * @param locale Locale to use for translations - */ -@Deprecated( - "The paginator has been replaced with much better, button-based variants. You can easily get at them " + - "from your commands by using the `paginator` DSL, or take a look at `InteractionButtonPaginator` or " + - "`MessageButtonPaginator` for interaction-based and message-based implementations respectively.", - - level = DeprecationLevel.WARNING -) -public open class Paginator( - public val pages: Pages, - public val targetChannel: MessageChannelBehavior? = null, - public val targetMessage: Message? = null, - public val owner: User? = null, - public val timeout: Long? = null, - public val keepEmbed: Boolean = true, - public val pingInReply: Boolean = true, - public val switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, - - locale: Locale? = null -) : KoinComponent { - /** Current instance of the bot. **/ - public open val bot: ExtensibleBot by inject() - - /** Kord instance, backing the ExtensibleBot. **/ - public val kord: Kord by inject() - - /** Locale to use for translations. **/ - public val locale: Locale = locale ?: bot.settings.i18nBuilder.defaultLocale - - /** What to do after the paginator times out. **/ - public val timeoutCallbacks: MutableList Unit> = mutableListOf() - - init { - if (targetChannel == null && targetMessage == null) { - throw IllegalArgumentException("Must provide either a target channel or target message") - } - } - - /** Basic emojis that should be added to every paginator. **/ - public open val emojis: Array = arrayOf( - FIRST_PAGE_EMOJI, - LEFT_EMOJI, - RIGHT_EMOJI, - LAST_PAGE_EMOJI, - ) - - /** Reactions to add to the paginator after it's been sent. **/ - public open val reactions: MutableList = mutableListOf() - - /** Whether this paginator is currently active and processing events. **/ - public open var active: Boolean = true - - /** Currently-displayed page index. **/ - public var currentPageNum: Int = 0 - - /** Currently-displayed page group. **/ - public var currentGroup: String = pages.defaultGroup - - /** Set of all page groups. **/ - public open var allGroups: List = pages.groups.map { it.key } - - /** Currently-displayed page object. **/ - public open var currentPage: Page = pages.get(currentGroup, currentPageNum) - - /** Convenience function to send the current page to the channel, editing if a message is passed. **/ - public open suspend fun sendCurrentPage(message: MessageBehavior? = null): MessageBehavior { - val groupEmoji = if (pages.groups.size > 1) { - currentGroup - } else { - null - } - - val builder = currentPage.build( - locale, - currentPageNum, - pages.size, - groupEmoji, - allGroups.indexOf(currentGroup), - allGroups.size - ) - - return if (message != null) { - message.edit { embed { builder() } } - } else if (targetChannel != null) { - targetChannel.createEmbed { builder() } - } else if (targetMessage != null) { - targetMessage.respond(pingInReply = pingInReply) { - embed { builder() } - } - } else { - throw IllegalArgumentException("Must provide either a target channel or target message") - } - } - - /** Send the embed to the channel given in the constructor. **/ - @KordPreview - public open suspend fun send() { - pages.validate() // Will throw if there's a problem - - val message = sendCurrentPage() - - if (pages.size > 1) { - reactions += emojis - } - - if (pages.groups.size > 1) { - reactions += switchEmoji - } - - if (message.getChannelOrNull() !is DmChannel && reactions.isNotEmpty()) { - reactions += FINISH_EMOJI - } - - if (reactions.isNotEmpty()) { - reactions.forEach { message.addReaction(it) } - - val guildCondition: suspend Event.() -> Boolean = { - this is ReactionAddEvent && - message.id == this.messageId && - this.userId != kord.selfId && - (owner == null || owner.id == this.userId) && - active - } - - val dmCondition: suspend Event.() -> Boolean = { - when (this) { - is ReactionAddEvent -> message.id == this.messageId && - this.userId != kord.selfId && - (owner == null || owner.id == this.userId) && - active - - is ReactionRemoveEvent -> message.id == this.messageId && - this.userId != kord.selfId && - (owner == null || owner.id == this.userId) && - active - - else -> false - } - } - - while (true) { - val condition = if (message.getChannelOrNull() is DmChannel) { - dmCondition - } else { - guildCondition - } - - val event = if (timeout != null) { - kord.waitFor(timeout = timeout, condition = condition) - } else { - kord.waitFor(condition = condition) - } ?: break - - processEvent(event) - } - - if (timeout != null) { - destroy(message) - runTimeoutCallbacks() - } - } else { - if (timeout != null) { - delay(timeout) - - if (!keepEmbed) { - destroy(message) - } - - runTimeoutCallbacks() - } - } - } - - /** - * Paginator event handler. - * - * @param event [Event] to process. - */ - public open suspend fun processEvent(event: Event) { - val emoji = when (event) { - is ReactionAddEvent -> event.emoji - is ReactionRemoveEvent -> event.emoji - - else -> error(WRONG_TYPE) - } - - val message = when (event) { - is ReactionAddEvent -> event.message.asMessage() - is ReactionRemoveEvent -> event.message.asMessage() - - else -> error(WRONG_TYPE) - } - - val userId = when (event) { - is ReactionAddEvent -> event.userId - is ReactionRemoveEvent -> event.userId - - else -> error(WRONG_TYPE) - } - - logger.debug { "Paginator received emoji ${emoji.name}" } - - val channel = message.getChannelOrNull() - - if (channel !is DmChannel) { - message.deleteReaction(userId, emoji) - } - - when (emoji) { - FIRST_PAGE_EMOJI -> goToPage(message, 0) - LEFT_EMOJI -> goToPage(message, currentPageNum - 1) - RIGHT_EMOJI -> goToPage(message, currentPageNum + 1) - LAST_PAGE_EMOJI -> goToPage(message, pages.size - 1) - FINISH_EMOJI -> if (channel !is DmChannel) destroy(message) - - switchEmoji -> switchGroup(message) - - else -> return - } - } - - /** Convenience function to switch the currently displayed group. **/ - public open suspend fun switchGroup(message: MessageBehavior) { - val current = currentGroup - val nextIndex = allGroups.indexOf(current) + 1 - - currentGroup = if (nextIndex >= allGroups.size) { - allGroups.first() - } else { - allGroups[nextIndex] - } - - currentPage = pages.get(currentGroup, currentPageNum) - - sendCurrentPage(message) - } - - /** - * Switch to another page in the current group. - * - * @param page Page number to display. - */ - public open suspend fun goToPage(message: MessageBehavior, page: Int) { - if (page == currentPageNum) { - return - } - - if (page < 0 || page > pages.size - 1) { - return - } - - currentPageNum = page - currentPage = pages.get(currentGroup, currentPageNum) - - sendCurrentPage(message) - } - - /** - * Destroy the paginator. - * - * This will stop the paginator from processing events, and delete its message if [keepEmbed] is `false`. - */ - public open suspend fun destroy(message: MessageBehavior) { - if (!active) { - return - } - - if (!keepEmbed) { - message.delete() - } else { - if (message.asMessage().getChannelOrNull() !is DmChannel) { - message.deleteAllReactions() - } else { - reactions.forEach { message.deleteOwnReaction(it) } - } - } - - active = false - } - - /** - * Register a callback that is called after the paginator times out. - * - * If there is no [timeout] set, your callbacks will never be called! - */ - public open fun onTimeout(body: suspend () -> Unit): Paginator { - timeoutCallbacks.add(body) - - return this - } - - /** @suppress Call the timeout callbacks. **/ - @Suppress("TooGenericExceptionCaught") // Come on, now. - public open suspend fun runTimeoutCallbacks() { - timeoutCallbacks.forEach { - try { - it.invoke() - } catch (t: Throwable) { - logger.error(t) { "Error thrown by timeout callback: $it" } - } - } - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt index 2ca8de2dd7..eb7cee3101 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/Converters.kt @@ -7,8 +7,8 @@ package com.kotlindiscord.kord.extensions.sentry +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import dev.kord.common.annotation.KordPreview import io.sentry.protocol.SentryId diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt index a15ab015e9..950f19c453 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt @@ -3,9 +3,9 @@ package com.kotlindiscord.kord.extensions.sentry import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter -import com.kotlindiscord.kord.extensions.commands.parser.Argument import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestChoiceEnum.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestChoiceEnum.kt index 138f8d9f5f..5091e39b44 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestChoiceEnum.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestChoiceEnum.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.test.bot -import com.kotlindiscord.kord.extensions.commands.slash.converters.ChoiceEnum +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceEnum enum class TestChoiceEnum(override val readableName: String) : ChoiceEnum { ONE("first"), diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 4cf76004fd..0f4dd80f43 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -1,22 +1,21 @@ -@file:OptIn(KordPreview::class, TranslationNotSupported::class) +@file:OptIn(KordPreview::class) package com.kotlindiscord.kord.extensions.test.bot +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.respond +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.enumChoice +import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.group +import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.commands.converters.impl.* -import com.kotlindiscord.kord.extensions.commands.parser.Arguments -import com.kotlindiscord.kord.extensions.commands.slash.AutoAckType -import com.kotlindiscord.kord.extensions.commands.slash.TranslationNotSupported -import com.kotlindiscord.kord.extensions.commands.slash.converters.impl.enumChoice -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.chatCommand -import com.kotlindiscord.kord.extensions.extensions.chatGroupCommand -import com.kotlindiscord.kord.extensions.extensions.slashCommand +import com.kotlindiscord.kord.extensions.components.AutoAckType +import com.kotlindiscord.kord.extensions.extensions.* import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.reply @@ -147,140 +146,136 @@ class TestExtension : Extension() { } } - slashCommand { + publicSlashCommand { name = "pages" description = "Pages!" - autoAck = AutoAckType.PUBLIC guild(787452339908116521) action { - paginator("short") { - owner = event.interaction.user.asUser() - timeoutSeconds = 60 - keepEmbed = false - - (0..2).forEach { - page( - Page { - description = "Short page $it." - - footer { - text = "Footer text ($it)" - } - } - ) - - page( - "Expanded", - - Page { - description = "Expanded page $it, expanded page $it\n" + - "Expanded page $it, expanded page $it" - - footer { - text = "Footer text ($it)" - } - } - ) - - page( - "MASSIVE GROUP", - - Page { - description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + - "MASSIVE PAGE $it, MASSIVE PAGE $it" - - footer { - text = "Footer text ($it)" - } - } - ) - } - }.send() +// paginator("short") { +// owner = event.interaction.user.asUser() +// timeoutSeconds = 60 +// keepEmbed = false +// +// (0..2).forEach { +// page( +// Page { +// description = "Short page $it." +// +// footer { +// text = "Footer text ($it)" +// } +// } +// ) +// +// page( +// "Expanded", +// +// Page { +// description = "Expanded page $it, expanded page $it\n" + +// "Expanded page $it, expanded page $it" +// +// footer { +// text = "Footer text ($it)" +// } +// } +// ) +// +// page( +// "MASSIVE GROUP", +// +// Page { +// description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + +// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + +// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + +// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + +// "MASSIVE PAGE $it, MASSIVE PAGE $it" +// +// footer { +// text = "Footer text ($it)" +// } +// } +// ) +// } +// }.send() } } - slashCommand { + ephemeralSlashCommand { name = "buttons" description = "Buttons!" guild(787452339908116521) // Our test server action { - ephemeralFollowUp { + respond { content = "Buttons!" - components(60) { - interactiveButton { - label = "Button one!" - - action { - respond("Button one pressed!") - } - } - - interactiveButton { - label = "Button two!" - style = ButtonStyle.Secondary - - action { - respond("Button two pressed!") - } - } - - disabledButton { - emoji("❎") - } - - linkButton { - label = "Google" - emoji("🔗") - - url = "https://google.com" - } - } +// components(60) { +// interactiveButton { +// label = "Button one!" +// +// action { +// respond("Button one pressed!") +// } +// } +// +// interactiveButton { +// label = "Button two!" +// style = ButtonStyle.Secondary +// +// action { +// respond("Button two pressed!") +// } +// } +// +// disabledButton { +// emoji("❎") +// } +// +// linkButton { +// label = "Google" +// emoji("🔗") +// +// url = "https://google.com" +// } +// } } } } - slashCommand { + publicSlashCommand { name = "test-noack" description = "Don't auto-ack this one" - autoAck = AutoAckType.NONE guild(787452339908116521) // Our test server - action { - ack(false) // Public ack - - publicFollowUp { - embed { - title = "An embed!" - description = "With a description, and without a content string!" - } + initialResponse { + embed { + title = "An embed!" + description = "With a description, and without a content string!" } } + + action { + } } - slashCommand(::SlashChoiceArgs) { + publicSlashCommand(::SlashChoiceArgs) { name = "choice" description = "Choice-based" - autoAck = AutoAckType.PUBLIC guild(787452339908116521) // Our test server action { - publicFollowUp { + respond { content = "Your choice: ${arguments.arg.readableName} -> ${arguments.arg.name}" } } } - slashCommand { + ephemeralSlashCommand { name = "group" description = "Test command, please ignore" @@ -289,14 +284,12 @@ class TestExtension : Extension() { group("one") { description = "Group one" - subCommand(::SlashArgs) { + publicSubCommand(::SlashArgs) { name = "test" description = "Test command, please ignore" - autoAck = AutoAckType.PUBLIC - action { - publicFollowUp { + respond { content = "Some content" embed { @@ -328,12 +321,12 @@ class TestExtension : Extension() { } } - subCommand { + ephemeralSubCommand { name = "test-two" description = "Test command, please ignore" action { - ephemeralFollowUp { + respond { content = "Some content" } } @@ -341,7 +334,7 @@ class TestExtension : Extension() { } } - slashCommand { + ephemeralSlashCommand { name = "guild-embed" description = "Test command, please ignore" @@ -350,14 +343,12 @@ class TestExtension : Extension() { group("first") { description = "First group." - subCommand(::SlashArgs) { + publicSubCommand(::SlashArgs) { name = "inner-test" description = "Test command, please ignore" - autoAck = AutoAckType.PUBLIC - action { - publicFollowUp { + respond { embed { title = "Guild response" description = "Guild description" @@ -389,17 +380,16 @@ class TestExtension : Extension() { } } - slashCommand(::SlashArgs) { + publicSlashCommand(::SlashArgs) { name = "test-embed" description = "Test command, please ignore\n\n" + "Now with some newlines in the description!" - autoAck = AutoAckType.PUBLIC guild(787452339908116521) // Our test server action { - publicFollowUp { + respond { embed { title = "Test response" description = "Test description" diff --git a/libs.versions.toml b/libs.versions.toml index a60b761fdc..159dd92a43 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,7 +1,7 @@ [versions] detekt = "1.17.1" # Note: Plugin versions must be updated in the settings.gradle.kts too dokka = "1.4.10.2" # Note: Plugin versions must be updated in the settings.gradle.kts too -kotlin = "1.5.10" # Note: Plugin versions must be updated in the settings.gradle.kts too +kotlin = "1.5.30" # Note: Plugin versions must be updated in the settings.gradle.kts too commons-text = "1.9" commons-validator = "1.7" @@ -13,8 +13,8 @@ konf = "0.23.0" #kord = "0.8.0-M4" kord = "0.8.0-M5" kotlinpoet = "1.8.0" -ksp = "1.5.10-1.0.0-beta02" -kx-ser = "1.2.1" +ksp = "1.5.30-1.0.0-beta08" +kx-ser = "1.2.2" linkie = "1.0.85" logback = "1.2.3" logging = "2.0.6" diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt index 4d5d89da6a..c9f6d6c94c 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt @@ -8,10 +8,10 @@ package com.kotlindiscord.kord.extensions.modules.time.java import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt index 86c413d854..34caad44ed 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt @@ -8,10 +8,10 @@ package com.kotlindiscord.kord.extensions.modules.time.java import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 8d3beeb8f2..07f05af42d 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.test.bot -import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.time.java.coalescedJ8Duration diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt index f1639f6c29..d0baafbad6 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt @@ -8,11 +8,11 @@ package com.kotlindiscord.kord.extensions.modules.time.time4j import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* import com.kotlindiscord.kord.extensions.commands.converters.impl.RegexCoalescingConverter -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt index 5f1f9fbaed..96df78ab7d 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt @@ -8,10 +8,10 @@ package com.kotlindiscord.kord.extensions.modules.time.time4j import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* -import com.kotlindiscord.kord.extensions.commands.parser.Argument -import com.kotlindiscord.kord.extensions.commands.parser.Arguments import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index a9df7d36ea..08fe3504cf 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.test.bot -import com.kotlindiscord.kord.extensions.commands.parser.Arguments +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.modules.time.time4j.coalescedT4jDuration diff --git a/settings.gradle.kts b/settings.gradle.kts index 502eafdffd..0e1e054b29 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,10 +7,10 @@ pluginManagement { plugins { // NOTE: UPDATE THIS IF YOU UPDATE THE LIBS.VERSIONS.TOML - kotlin("jvm") version "1.5.10" - kotlin("plugin.serialization") version "1.5.10" + kotlin("jvm") version "1.5.30" + kotlin("plugin.serialization") version "1.5.30" - id("com.google.devtools.ksp") version "1.5.10-1.0.0-beta02" + id("com.google.devtools.ksp") version "1.5.30-1.0.0-beta08" id("io.gitlab.arturbosch.detekt") version "1.17.1" id("org.jetbrains.dokka") version "1.4.10.2" } From c327ca793f41461db9a258c54254924b992158c5 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 31 Aug 2021 12:56:51 +0100 Subject: [PATCH 019/131] Commit for the evening --- .../application/ApplicationCommandRegistry.kt | 171 +++++++++++++++++- 1 file changed, 169 insertions(+), 2 deletions(-) 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 84de0f18c3..a6e5f832d8 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 @@ -4,14 +4,23 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot 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.slash.SlashCommandParser +import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import dev.kord.common.entity.Snowflake import dev.kord.core.Kord -import dev.kord.core.entity.application.UserCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import dev.kord.core.firstOrNull +import kotlinx.coroutines.flow.map +import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject /** Registry for all Discord application commands. **/ public open class ApplicationCommandRegistry : KoinComponent { + private val logger = KotlinLogging.logger { } + /** Current instance of the bot. **/ public open val bot: ExtensibleBot by inject() @@ -24,6 +33,112 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() + /** Mapping of Discord-side command ID to a message command object. **/ + public val messageCommands: MutableMap> = mutableMapOf() + + /** Mapping of Discord-side command ID to a slash command object. **/ + public val slashCommands: MutableMap> = mutableMapOf() + + /** Mapping of Discord-side command ID to a user command object. **/ + public val userCommands: MutableMap> = mutableMapOf() + + public suspend fun setup() { + + } + + public suspend fun initialRegistration() { + if (!bot.settings.applicationCommandsBuilder.register) { + logger.debug { + "Application command registration is disabled, pairing existing commands with extension commands." + } + } + + val commands: MutableList> = mutableListOf() + + bot.extensions.values.forEach { + commands += it.messageCommands + commands += it.slashCommands + commands += it.userCommands + } + + syncAll(true, commands) + } + + // region: Untyped sync functions + + /** Register multiple generic application commands. **/ + public open suspend fun syncAll(removeOthers: Boolean = false, commands: List>) { + val groupedCommands = commands.groupBy { it.guildId } + } + + /** Register multiple generic application commands. **/ + public open suspend fun sync( + removeOthers: Boolean = false, + guildId: Snowflake?, + commands: List> + ) { + // NOTE: Someday, discord will make real i18n possible, we hope... + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (guildId != null) { + kord.getGuild(guildId) + ?: return logger.warn { + "Cannot register application commands for guild ID ${guildId.asString}, " + + "as it seems to be missing." + } + } else { + null + } + + // Get guild commands if we're registering them (guild != null), otherwise get global commands + val registered = guild?.commands?.map { it.name to it.id } + ?: kord.globalCommands.map { it.name to it.id } + + if (!bot.settings.applicationCommandsBuilder.register) { + commands.forEach { commandObj -> + val existingCommand = registered.firstOrNull { it.first == commandObj.getTranslatedName(locale) } + + if (existingCommand != null) { + when (commandObj) { + is MessageCommand<*> -> messageCommands[existingCommand.second] = commandObj + is SlashCommand<*, *> -> slashCommands[existingCommand.second] = commandObj + is UserCommand<*> -> userCommands[existingCommand.second] = commandObj + } + } + } + + return // We're only syncing them up, there's no other API work to do + } + } + + /** Register a generic application command. **/ + public open suspend fun registerGeneric(command: ApplicationCommand<*>) { + TODO() + } + + // endregion + + // region: Typed batch registration functions + + /** Register multiple message commands. **/ + public open suspend fun registerAll(vararg commands: MessageCommand<*>) { + TODO() + } + + /** Register multiple slash commands. **/ + public open suspend fun registerAll(vararg commands: SlashCommand<*, *>) { + TODO() + } + + /** Register multiple user commands. **/ + public open suspend fun registerAll(vararg commands: UserCommand<*>) { + TODO() + } + + // endregion + + // region: Typed registration functions + /** Register a message command. **/ public open suspend fun register(command: MessageCommand<*>) { TODO() @@ -35,7 +150,59 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Register a user command. **/ - public open suspend fun register(command: UserCommand) { + public open suspend fun register(command: UserCommand<*>) { + TODO() + } + + // endregion + + // region: Typed unregistration functions + + /** Unregister a message command. **/ + public open suspend fun unregister(command: MessageCommand<*>) { + TODO() + } + + /** Unregister a slash command. **/ + public open suspend fun unregister(command: SlashCommand<*, *>) { + TODO() + } + + /** Unregister a user command. **/ + public open suspend fun unregister(command: UserCommand<*>) { TODO() } + + // endregion + + // region: Event handlers + + public suspend fun handle(event: MessageCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = messageCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown message command: ${commandId.asString}" } + + command.call(event) + } + + public suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { + val commandId = event.interaction.command.rootId + val command = slashCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown slash command: ${commandId.asString}" } + + command.call(event) + } + + public suspend fun handle(event: UserCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = userCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown user command: ${commandId.asString}" } + + command.call(event) + } + + // endregion } From be6d4d9eca3be1639be4d73ad7b1e38ef83fe27a Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 1 Sep 2021 14:50:50 +0100 Subject: [PATCH 020/131] Application commands are now fully implemented. --- .../kord/extensions/ExtensibleBot.kt | 18 +- .../builders/ExtensibleBotBuilder.kt | 108 +++- .../application/ApplicationCommand.kt | 4 + .../application/ApplicationCommandRegistry.kt | 467 ++++++++++++++++-- .../application/message/MessageCommand.kt | 38 ++ .../message/MessageCommandContext.kt | 2 +- .../application/slash/SlashCommand.kt | 40 +- .../commands/application/user/UserCommand.kt | 38 ++ .../application/user/UserCommandContext.kt | 2 +- .../commands/slash/SlashCommandRegistry.kt | 346 ------------- .../kord/extensions/extensions/Extension.kt | 77 +-- .../kord/extensions/extensions/_Commands.kt | 145 +++++- .../kord/extensions/test/bot/Bot.kt | 2 + .../kord/extensions/test/bot/TestExtension.kt | 28 ++ 14 files changed, 842 insertions(+), 473 deletions(-) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index ee1216f637..bc088778e7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -4,9 +4,9 @@ package com.kotlindiscord.kord.extensions import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.events.ExtensionEvent import com.kotlindiscord.kord.extensions.extensions.Extension @@ -21,6 +21,8 @@ import dev.kord.core.event.gateway.DisconnectEvent import dev.kord.core.event.gateway.ReadyEvent import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.on import dev.kord.gateway.Intents @@ -151,11 +153,11 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } on { - if (!initialized) { // We do this because a reconnect will cause this event to happen again. + if (!initialized) { // We do this because a reconnection will cause this event to happen again. initialized = true if (settings.applicationCommandsBuilder.enabled) { - getKoin().get().syncAll() + getKoin().get().initialRegistration() } else { logger.info { "Slash command support is disabled - set `enabled` to `true` in the `slashCommands` builder" + @@ -175,7 +177,15 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva if (settings.applicationCommandsBuilder.enabled) { on { - getKoin().get().handle(this) + getKoin().get().handle(this) + } + + on { + getKoin().get().handle(this) + } + + on { + getKoin().get().handle(this) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 5548d8e40b..48f4b568c1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -6,8 +6,8 @@ import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.BotBuilderDSL import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations import com.kotlindiscord.kord.extensions.i18n.SupportedLocales @@ -28,6 +28,8 @@ import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.builder.kord.Shards import dev.kord.core.cache.KordCacheBuilder import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy @@ -76,7 +78,7 @@ public open class ExtensibleBotBuilder { public val membersBuilder: MembersBuilder = MembersBuilder() /** @suppress Builder that shouldn't be set directly by the user. **/ - public val messageCommandsBuilder: MessageCommandsBuilder = MessageCommandsBuilder() + public val messageCommandsBuilder: ChatCommandsBuilder = ChatCommandsBuilder() /** @suppress Builder that shouldn't be set directly by the user. **/ public var presenceBuilder: PresenceBuilder.() -> Unit = { status = PresenceStatus.Online } @@ -131,10 +133,10 @@ public open class ExtensibleBotBuilder { /** * DSL function used to configure the bot's message command options. * - * @see MessageCommandsBuilder + * @see ChatCommandsBuilder */ @BotBuilderDSL - public suspend fun messageCommands(builder: suspend MessageCommandsBuilder.() -> Unit) { + public suspend fun messageCommands(builder: suspend ChatCommandsBuilder.() -> Unit) { builder(messageCommandsBuilder) } @@ -226,7 +228,12 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } loadModule { single { messageCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } - loadModule { single { applicationCommandsBuilder.slashRegistryBuilder() } bind SlashCommandRegistry::class } + + loadModule { + single { + applicationCommandsBuilder.applicationCommandRegistryBuilder() + } bind ApplicationCommandRegistry::class + } loadModule { single { @@ -785,14 +792,14 @@ public open class ExtensibleBotBuilder { /** Builder used for configuring the bot's message command options. **/ @BotBuilderDSL - public class MessageCommandsBuilder { - /** Whether to invoke commands on bot mentions, in addition to using message prefixes. Defaults to `true`. **/ + public class ChatCommandsBuilder { + /** Whether to invoke commands on bot mentions, in addition to using chat prefixes. Defaults to `true`. **/ public var invokeOnMention: Boolean = true /** Prefix to require for command invocations on Discord. Defaults to `"!"`. **/ public var defaultPrefix: String = "!" - /** Whether to register and process message commands. Defaults to `false`. **/ + /** Whether to register and process chat commands. Defaults to `false`. **/ public var enabled: Boolean = false /** Number of threads to use for command execution. Defaults to twice the number of CPU threads. **/ @@ -858,8 +865,8 @@ public open class ExtensibleBotBuilder { /** Builder used for configuring the bot's application command options. **/ @BotBuilderDSL public class ApplicationCommandsBuilder { - /** Whether to register and process application commands. Defaults to `false`. **/ - public var enabled: Boolean = false + /** Whether to register and process application commands. Defaults to `true`. **/ + public var enabled: Boolean = true /** The guild ID to use for all global application commands. Intended for testing. **/ public var defaultGuild: Snowflake? = null @@ -868,14 +875,29 @@ public open class ExtensibleBotBuilder { public var register: Boolean = true /** @suppress Builder that shouldn't be set directly by the user. **/ - public var slashRegistryBuilder: () -> SlashCommandRegistry = { SlashCommandRegistry() } + public var applicationCommandRegistryBuilder: () -> ApplicationCommandRegistry = + { ApplicationCommandRegistry() } + + /** + * List of message command checks. + * + * These checks will be checked against all message commands. + */ + public val messageCommandChecks: MutableList> = mutableListOf() /** * List of slash command checks. * * These checks will be checked against all slash commands. */ - public val slashCheckList: MutableList> = mutableListOf() + public val slashCommandChecks: MutableList> = mutableListOf() + + /** + * List of user command checks. + * + * These checks will be checked against all user commands. + */ + public val userCommandChecks: MutableList> = mutableListOf() /** Set a guild ID to use for all global application commands. Intended for testing. **/ public fun defaultGuild(id: Snowflake) { @@ -896,8 +918,33 @@ public open class ExtensibleBotBuilder { * Register the builder used to create the [SlashCommandRegistry]. You can change this if you need to make * use of a subclass. */ - public fun slashRegistry(builder: () -> SlashCommandRegistry) { - slashRegistryBuilder = builder + public fun applicationCommandRegistry(builder: () -> ApplicationCommandRegistry) { + applicationCommandRegistryBuilder = builder + } + + /** + * Define a check which must pass for a message command to be executed. This check will be applied to all + * message commands. + * + * A message command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all slash commands. + */ + public fun messageCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } + } + + /** + * Overloaded message command check function to allow for DSL syntax. + * + * @param check Check to apply to all slash commands. + */ + public fun messageCommandCheck(check: Check) { + slashCommandChecks.add(check) } /** @@ -912,8 +959,8 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun slashCheck(vararg checks: Check) { - checks.forEach { slashCheckList.add(it) } + public fun slashCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } } /** @@ -921,8 +968,33 @@ public open class ExtensibleBotBuilder { * * @param check Check to apply to all slash commands. */ - public fun slashCheck(check: Check) { - slashCheckList.add(check) + public fun slashCommandCheck(check: Check) { + slashCommandChecks.add(check) + } + + /** + * Define a check which must pass for a user command to be executed. This check will be applied to all + * user commands. + * + * A user command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all slash commands. + */ + public fun userCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } + } + + /** + * Overloaded user command check function to allow for DSL syntax. + * + * @param check Check to apply to all slash commands. + */ + public fun userCommandCheck(check: Check) { + slashCommandChecks.add(check) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 22a250af53..c56f2199ef 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.utils.getLocale +import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Permission import dev.kord.common.entity.Snowflake import dev.kord.core.Kord @@ -44,6 +45,9 @@ public abstract class ApplicationCommand( /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() + /** Discord-side command type, for matching up. **/ + public abstract val type: ApplicationCommandType + /** @suppress **/ public open val checkList: MutableList> = mutableListOf() 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 a6e5f832d8..d0cc9d4c62 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 @@ -1,3 +1,10 @@ +@file:Suppress( + "TooGenericExceptionCaught", + "StringLiteralDuplication", + "AnnotationSpacing", + "SpacingBetweenAnnotations" +) + package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.ExtensibleBot @@ -5,17 +12,21 @@ import com.kotlindiscord.kord.extensions.commands.application.message.MessageCom import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandParser import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand +import com.kotlindiscord.kord.extensions.commands.converters.SlashCommandConverter import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Snowflake import dev.kord.core.Kord +import dev.kord.core.behavior.createApplicationCommands import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent -import dev.kord.core.firstOrNull -import kotlinx.coroutines.flow.map +import dev.kord.rest.builder.interaction.* +import kotlinx.coroutines.flow.toList import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import java.util.* /** Registry for all Discord application commands. **/ public open class ApplicationCommandRegistry : KoinComponent { @@ -42,14 +53,24 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Mapping of Discord-side command ID to a user command object. **/ public val userCommands: MutableMap> = mutableMapOf() - public suspend fun setup() { + /** Whether the initial sync has been finished, and commands should be registered directly. **/ + public var initialised: Boolean = false - } + /** Quick access to the human-readable name for a Discord application command type. **/ + public val ApplicationCommandType.name: String + get() = when (this) { + is ApplicationCommandType.Unknown -> "unknown" + ApplicationCommandType.ChatInput -> "slash" + ApplicationCommandType.Message -> "message" + ApplicationCommandType.User -> "user" + } + + /** Handles the initial registration of commands, after extensions have been loaded. **/ public suspend fun initialRegistration() { if (!bot.settings.applicationCommandsBuilder.register) { logger.debug { - "Application command registration is disabled, pairing existing commands with extension commands." + "Application command registration is disabled, pairing existing commands with extension commands" } } @@ -61,7 +82,13 @@ public open class ApplicationCommandRegistry : KoinComponent { commands += it.userCommands } - syncAll(true, commands) + try { + syncAll(true, commands) + } catch (t: Throwable) { + logger.error(t) { "Failed to synchronise application commands" } + } + + initialised = true } // region: Untyped sync functions @@ -69,6 +96,64 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Register multiple generic application commands. **/ public open suspend fun syncAll(removeOthers: Boolean = false, commands: List>) { val groupedCommands = commands.groupBy { it.guildId } + + groupedCommands.forEach { + try { + sync(removeOthers, it.key, it.value) + } catch (t: Throwable) { + logger.error(t) { + if (it.key == null) { + "Failed to synchronise global application commands" + } else { + "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" + } + } + } + } + + val commandsWithPerms = (messageCommands + slashCommands + userCommands) + .filterValues { + it.allowedRoles.isNotEmpty() || + it.allowedUsers.isNotEmpty() || + it.disallowedRoles.isNotEmpty() || + it.disallowedUsers.isNotEmpty() || + !it.allowByDefault + } + .toList() + .groupBy { it.second.guildId } + + try { + commandsWithPerms.forEach { (guildId, commands) -> + if (guildId != null) { + kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guildId) { + commands.forEach { (id, commandObj) -> + command(id) { + commandObj.allowedUsers.map { user(it, true) } + commandObj.disallowedUsers.map { user(it, false) } + + commandObj.allowedRoles.map { role(it, true) } + commandObj.disallowedRoles.map { role(it, false) } + } + } + } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions - for this reason, all commands with configured" + + "permissions will be disabled." + } + + commandsWithPerms.forEach { (_, commands) -> + commands.forEach { (id, _) -> + messageCommands.remove(id) + slashCommands.remove(id) + userCommands.remove(id) + } + } + } } /** Register multiple generic application commands. **/ @@ -91,29 +176,202 @@ public open class ApplicationCommandRegistry : KoinComponent { } // Get guild commands if we're registering them (guild != null), otherwise get global commands - val registered = guild?.commands?.map { it.name to it.id } - ?: kord.globalCommands.map { it.name to it.id } + val registered = guild?.commands?.toList() + ?: kord.globalCommands.toList() if (!bot.settings.applicationCommandsBuilder.register) { commands.forEach { commandObj -> - val existingCommand = registered.firstOrNull { it.first == commandObj.getTranslatedName(locale) } + val existingCommand = registered.firstOrNull { commandObj.matches(locale, it) } if (existingCommand != null) { when (commandObj) { - is MessageCommand<*> -> messageCommands[existingCommand.second] = commandObj - is SlashCommand<*, *> -> slashCommands[existingCommand.second] = commandObj - is UserCommand<*> -> userCommands[existingCommand.second] = commandObj + is MessageCommand<*> -> messageCommands[existingCommand.id] = commandObj + is SlashCommand<*, *> -> slashCommands[existingCommand.id] = commandObj + is UserCommand<*> -> userCommands[existingCommand.id] = commandObj } } } return // We're only syncing them up, there's no other API work to do } + + // Extension commands that haven't been registered yet + val toAdd = commands.filter { c -> registered.all { !c.matches(locale, it) } } + + // Extension commands that were previously registered + val toUpdate = commands.filter { c -> registered.any { c.matches(locale, it) } } + + // Registered Discord commands that haven't been provided by extensions + val toRemove = registered.filter { c -> commands.all { !it.matches(locale, c) } } + + logger.info { + if (guild == null) { + "Global application commands: ${toAdd.size} to add / " + + "${toUpdate.size} to update / " + + "${toRemove.size} to remove" + } else { + "Application commands for guild ${guild.name}: ${toAdd.size} to add / " + + "${toUpdate.size} to update / " + + "${toRemove.size} to remove" + } + } + + val toCreate = toAdd + toUpdate + + @Suppress("IfThenToElvis") // Ultimately, this is far more readable +val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalApplicationCommands { + toCreate.forEach { + val name = it.getTranslatedName(locale) + + logger.debug { "Adding/updating global ${it.type.name} command: $name" } + + when (it) { + is MessageCommand<*> -> message(name) { this.register(locale, it) } + is UserCommand<*> -> user(name) { this.register(locale, it) } + + is SlashCommand<*, *> -> input( + name, translationsProvider.translate(it.description, it.extension.bundle, locale = locale) + ) { this.register(locale, it) } + } + } + }.toList() + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createApplicationCommands { + toCreate.forEach { + val name = it.getTranslatedName(locale) + + logger.debug { "Adding/updating guild-specific ${it.type.name} command: $name" } + + when (it) { + is MessageCommand<*> -> message(name) { this.register(locale, it) } + is UserCommand<*> -> user(name) { this.register(locale, it) } + + is SlashCommand<*, *> -> input( + name, translationsProvider.translate(it.description, it.extension.bundle, locale = locale) + ) { this.register(locale, it) } + } + } + }.toList() + } + + // Next, we need to associate all the commands we just registered with the commands in our extensions + toCreate.forEach { command -> + val match = response.first { command.matches(locale, it) } + + when (command) { + is MessageCommand<*> -> messageCommands[match.id] = command + is SlashCommand<*, *> -> slashCommands[match.id] = command + is UserCommand<*> -> userCommands[match.id] = command + } + } + + // Finally, we can remove anything that needs to be removed + toRemove.forEach { + logger.debug { "Removing ${it.type.name} command: ${it.name}" } + it.delete() + } + + logger.info { + if (guild == null) { + "Finished synchronising global application commands" + } else { + "Finished synchronising application commands for guild ${guild.name}" + } + } } /** Register a generic application command. **/ - public open suspend fun registerGeneric(command: ApplicationCommand<*>) { - TODO() + public open suspend fun registerGeneric(command: ApplicationCommand<*>): ApplicationCommand<*>? { + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (command.guildId != null) { + kord.getGuild(command.guildId!!) + } else { + null + } + + val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalApplicationCommands { + val name = command.getTranslatedName(locale) + + logger.debug { "Adding/updating global ${command.type.name} command: $name" } + + when (command) { + is MessageCommand<*> -> message(name) { this.register(locale, command) } + is UserCommand<*> -> user(name) { this.register(locale, command) } + + is SlashCommand<*, *> -> input( + name, + + translationsProvider.translate( + command.description, + command.extension.bundle, + locale = locale + ) + ) { this.register(locale, command) } + } + }.toList() + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createApplicationCommands { + val name = command.getTranslatedName(locale) + + logger.debug { "Adding/updating guild-specific ${command.type.name} command: $name" } + + when (command) { + is MessageCommand<*> -> message(name) { this.register(locale, command) } + is UserCommand<*> -> user(name) { this.register(locale, command) } + + is SlashCommand<*, *> -> input( + name, + + translationsProvider.translate( + command.description, + command.extension.bundle, + locale = locale + ) + ) { this.register(locale, command) } + } + }.toList() + } + + val match = response.first { command.matches(locale, it) } + + try { + if (guild != null) { + kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, match.id) { + command.allowedUsers.map { user(it, true) } + command.disallowedUsers.map { user(it, false) } + + command.allowedRoles.map { role(it, true) } + command.disallowedRoles.map { role(it, false) } + } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions. This command will not be registered." + } + + return null + } + + when (command) { + is MessageCommand<*> -> messageCommands[match.id] = command + is SlashCommand<*, *> -> slashCommands[match.id] = command + is UserCommand<*> -> userCommands[match.id] = command + } + + return command } // endregion @@ -121,62 +379,95 @@ public open class ApplicationCommandRegistry : KoinComponent { // region: Typed batch registration functions /** Register multiple message commands. **/ - public open suspend fun registerAll(vararg commands: MessageCommand<*>) { - TODO() - } + public open suspend fun registerAll(vararg commands: MessageCommand<*>): List> = + commands.map { + try { + registerGeneric(it) as MessageCommand<*> + } catch (t: Throwable) { + logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } + + null + } + }.filterNotNull() /** Register multiple slash commands. **/ - public open suspend fun registerAll(vararg commands: SlashCommand<*, *>) { - TODO() - } + public open suspend fun registerAll(vararg commands: SlashCommand<*, *>): List> = + commands.map { + try { + registerGeneric(it) as SlashCommand<*, *> + } catch (t: Throwable) { + logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } + + null + } + }.filterNotNull() /** Register multiple user commands. **/ - public open suspend fun registerAll(vararg commands: UserCommand<*>) { - TODO() - } + public open suspend fun registerAll(vararg commands: UserCommand<*>): List> = + commands.map { + try { + registerGeneric(it) as UserCommand<*> + } catch (t: Throwable) { + logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } + + null + } + }.filterNotNull() // endregion // region: Typed registration functions /** Register a message command. **/ - public open suspend fun register(command: MessageCommand<*>) { - TODO() - } + public open suspend fun register(command: MessageCommand<*>): MessageCommand<*> = + registerGeneric(command) as MessageCommand<*> /** Register a slash command. **/ - public open suspend fun register(command: SlashCommand<*, *>) { - TODO() - } + public open suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *> = + registerGeneric(command) as SlashCommand<*, *> /** Register a user command. **/ - public open suspend fun register(command: UserCommand<*>) { - TODO() - } + public open suspend fun register(command: UserCommand<*>): UserCommand<*> = + registerGeneric(command) as UserCommand<*> // endregion - // region: Typed unregistration functions + // region: Unregistration functions /** Unregister a message command. **/ - public open suspend fun unregister(command: MessageCommand<*>) { + public open suspend fun unregisterGeneric(command: ApplicationCommand<*>) { TODO() } + /** Unregister a message command. **/ + public open suspend fun unregister(command: MessageCommand<*>): MessageCommand<*>? { + val filtered = messageCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + return messageCommands.remove(id) + } + /** Unregister a slash command. **/ - public open suspend fun unregister(command: SlashCommand<*, *>) { - TODO() + public open suspend fun unregister(command: SlashCommand<*, *>): SlashCommand<*, *>? { + val filtered = slashCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + return slashCommands.remove(id) } /** Unregister a user command. **/ - public open suspend fun unregister(command: UserCommand<*>) { - TODO() + public open suspend fun unregister(command: UserCommand<*>): UserCommand<*>? { + val filtered = userCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + return userCommands.remove(id) } // endregion // region: Event handlers + /** Event handler for message commands. **/ public suspend fun handle(event: MessageCommandInteractionCreateEvent) { val commandId = event.interaction.invokedCommandId val command = messageCommands[commandId] @@ -186,6 +477,7 @@ public open class ApplicationCommandRegistry : KoinComponent { command.call(event) } + /** Event handler for slash commands. **/ public suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { val commandId = event.interaction.command.rootId val command = slashCommands[commandId] @@ -195,6 +487,7 @@ public open class ApplicationCommandRegistry : KoinComponent { command.call(event) } + /** Event handler for user commands. **/ public suspend fun handle(event: UserCommandInteractionCreateEvent) { val commandId = event.interaction.invokedCommandId val command = userCommands[commandId] @@ -205,4 +498,102 @@ public open class ApplicationCommandRegistry : KoinComponent { } // endregion + + // region: Extensions + + /** Registration logic for slash commands, extracted for clarity. **/ + public suspend fun ChatInputCreateBuilder.register(locale: Locale, command: SlashCommand<*, *>) { + this.defaultPermission = command.guildId == null || command.allowByDefault + + if (command.hasBody) { + val args = command.arguments?.invoke() + + if (args != null) { + args.args.forEach { arg -> + val converter = arg.converter + + if (converter !is SlashCommandConverter) { + error("Argument ${arg.displayName} does not support slash commands.") + } + + if (this.options == null) this.options = mutableListOf() + + // TODO: It's impossible to translate these right now + this.options!! += converter.toSlashOption(arg) + } + } + } else { + command.subCommands.forEach { + val args = it.arguments?.invoke()?.args?.map { arg -> + val converter = arg.converter + + if (converter !is SlashCommandConverter) { + error("Argument ${arg.displayName} does not support slash commands.") + } + + // TODO: It's impossible to translate these right now + converter.toSlashOption(arg) + } + + this.subCommand( + it.name, + translationsProvider.translate(it.description, command.extension.bundle, locale = locale) + ) { + if (args != null) { + if (this.options == null) this.options = mutableListOf() + + this.options!!.addAll(args) + } + } + } + + command.groups.values.forEach { group -> + this.group(group.name, group.description) { + group.subCommands.forEach { + val args = it.arguments?.invoke()?.args?.map { arg -> + val converter = arg.converter + + if (converter !is SlashCommandConverter) { + error("Argument ${arg.displayName} does not support slash commands.") + } + + // TODO: It's impossible to translate these right now + converter.toSlashOption(arg) + } + + this.subCommand( + it.name, + translationsProvider.translate(it.description, command.extension.bundle, locale = locale) + ) { + if (args != null) { + if (this.options == null) this.options = mutableListOf() + + this.options!!.addAll(args) + } + } + } + } + } + } + } + + /** Registration logic for message commands, extracted for clarity. **/ + @Suppress("UnusedPrivateMember") // Only for now... + public fun MessageCommandCreateBuilder.register(locale: Locale, command: MessageCommand<*>) { + this.defaultPermission = command.guildId == null || command.allowByDefault + } + + /** Registration logic for user commands, extracted for clarity. **/ + @Suppress("UnusedPrivateMember") // Only for now... + public fun UserCommandCreateBuilder.register(locale: Locale, command: UserCommand<*>) { + this.defaultPermission = command.guildId == null || command.allowByDefault + } + + /** Check whether the type and name of an extension-registered application command matches a Discord one. **/ + public fun ApplicationCommand<*>.matches( + locale: Locale, + other: dev.kord.core.entity.application.ApplicationCommand + ): Boolean = getTranslatedName(locale) == other.name && type == other.type + + // endregion } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 9378359ba1..72c0a41179 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -2,13 +2,16 @@ package com.kotlindiscord.kord.extensions.commands.application.message import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.common.entity.ApplicationCommandType import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel @@ -26,6 +29,8 @@ public abstract class MessageCommand>( /** Command body, to be called when the command is executed. **/ public lateinit var body: suspend C.() -> Unit + override val type: ApplicationCommandType = ApplicationCommandType.Message + /** Call this to supply a command [body], to be called when the command is executed. **/ public fun action(action: suspend C.() -> Unit) { body = action @@ -101,6 +106,39 @@ public abstract class MessageCommand>( } } + override suspend fun runChecks(event: MessageCommandInteractionCreateEvent): Boolean { + val locale = event.getLocale() + val result = super.runChecks(event) + + if (result) { + settings.applicationCommandsBuilder.messageCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + extension.messageCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + } + + return result + } + /** A general way to handle errors thrown during the course of a command's execution. **/ public open suspend fun handleError(context: C, t: Throwable) { logger.error(t) { "Error during execution of $name message command (${context.event})" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt index edb5c36868..75790702e3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommandContext.kt @@ -15,5 +15,5 @@ public abstract class MessageCommandContext>( public override val command: MessageCommand, ) : ApplicationCommandContext(event, command) { /** Messages that this message command is being executed against. **/ - public val targetMessages: Collection = event.interaction.messages?.values ?: listOf() + public val targetMessages: Collection by lazy { event.interaction.messages?.values ?: listOf() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 4a9bd3606d..3d2e38329c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -2,14 +2,17 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Snowflake import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel @@ -50,7 +53,9 @@ public abstract class SlashCommand, A : Arguments> /** List of subcommands, if any. **/ public open val subCommands: MutableList> = mutableListOf() - override var guildId: Snowflake? = if (parentCommand == null && parentGroup == null) { + override val type: ApplicationCommandType = ApplicationCommandType.ChatInput + + override var guildId: Snowflake? = if (parentCommand != null && parentGroup != null) { settings.applicationCommandsBuilder.defaultGuild } else { null @@ -158,6 +163,39 @@ public abstract class SlashCommand, A : Arguments> } } + override suspend fun runChecks(event: ChatInputCommandInteractionCreateEvent): Boolean { + val locale = event.getLocale() + val result = super.runChecks(event) + + if (result) { + settings.applicationCommandsBuilder.slashCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + extension.slashCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + } + + return result + } + /** A general way to handle errors thrown during the course of a command's execution. **/ public open suspend fun handleError(context: C, t: Throwable, commandObj: SlashCommand<*, *>) { logger.error(t) { "Error during execution of ${commandObj.name} slash command (${context.event})" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 26753a00cf..f0499a5d27 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -2,13 +2,16 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.common.entity.ApplicationCommandType import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel @@ -26,6 +29,8 @@ public abstract class UserCommand>( /** Command body, to be called when the command is executed. **/ public lateinit var body: suspend C.() -> Unit + override val type: ApplicationCommandType = ApplicationCommandType.User + /** Call this to supply a command [body], to be called when the command is executed. **/ public fun action(action: suspend C.() -> Unit) { body = action @@ -101,6 +106,39 @@ public abstract class UserCommand>( } } + override suspend fun runChecks(event: UserCommandInteractionCreateEvent): Boolean { + val locale = event.getLocale() + val result = super.runChecks(event) + + if (result) { + settings.applicationCommandsBuilder.userCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + extension.userCommandChecks.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + } + + return result + } + /** A general way to handle errors thrown during the course of a command's execution. **/ public open suspend fun handleError(context: C, t: Throwable) { logger.error(t) { "Error during execution of $name user command (${context.event})" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt index 741f89bae3..71b69ac070 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommandContext.kt @@ -15,5 +15,5 @@ public abstract class UserCommandContext>( public override val command: UserCommand ) : ApplicationCommandContext(event, command) { /** Messages that this message command is being executed against. **/ - public val targetUsers: Collection = event.interaction.users?.values ?: listOf() + public val targetUsers: Collection by lazy { event.interaction.users?.values ?: listOf() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt deleted file mode 100644 index 81005c5f3b..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/slash/SlashCommandRegistry.kt +++ /dev/null @@ -1,346 +0,0 @@ -@file:Suppress("StringLiteralDuplication") - -package com.kotlindiscord.kord.extensions.commands.slash - -import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand -import com.kotlindiscord.kord.extensions.commands.converters.SlashCommandConverter -import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.Snowflake -import dev.kord.core.Kord -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.builder.interaction.ChatInputCreateBuilder -import dev.kord.rest.builder.interaction.group -import dev.kord.rest.builder.interaction.subCommand -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList -import mu.KLogger -import mu.KotlinLogging -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -private val logger: KLogger = KotlinLogging.logger {} - -/** - * Class responsible for keeping track of slash commands, registering and executing them. - * - * Currently only single-level commands are supported, no command groups or subcommands. - */ -@OptIn(KordPreview::class) -public open class SlashCommandRegistry : KoinComponent { - /** Current instance of the bot. **/ - public open val bot: ExtensibleBot by inject() - - /** Kord instance, backing the ExtensibleBot. **/ - public val kord: Kord by inject() - - /** Translations provider, for retrieving translations. **/ - public val translationsProvider: TranslationsProvider by inject() - - /** @suppress **/ - public open val commands: MutableMap>> = mutableMapOf( - null to mutableListOf() // So that global commands always have a list here - ) - - /** @suppress **/ - public open val commandMap: MutableMap> = mutableMapOf() - -// TODO: Sentry? -// private val sentry: SentryAdapter by bot.koin.inject() - - /** Register a slash command here, before they're synced to Discord. **/ - public open fun register(command: SlashCommand<*, *>, guild: Snowflake? = null): Boolean { - val locale = bot.settings.i18nBuilder.defaultLocale - - commands.putIfAbsent(guild, mutableListOf()) - - val args = command.arguments?.invoke() - var lastArgRequired = true // Start with `true` because required args must come first - - args?.args?.forEach { arg -> - if (arg.converter !is SlashCommandConverter) { - error("Argument ${arg.displayName} does not support slash commands.") - } - - if (arg.converter.required && !lastArgRequired) { - error("Required arguments must be placed before non-required arguments.") - } - - lastArgRequired = arg.converter.required - } - - val exists = commands[guild]!!.any { it.name == command.getTranslatedName(locale) } - - if (exists) { - return false - } - - commands[guild]!!.add(command) - - return true - } - - /** - * Sync all slash commands to Discord, removing unrecognised ones. - * - * Note that Discord doesn't let us get a list of guilds we have commands on, so we can't - * remove commands for guilds the bot isn't present on. - */ - @Suppress("TooGenericExceptionCaught") // Better safe than sorry - public open suspend fun syncAll() { - logger.info { "Synchronising slash commands. This may take some time." } - - if (!bot.settings.applicationCommandsBuilder.register) { - logger.debug { - "Slash command registration is disabled, pairing existing commands with extension commands." - } - } - - try { - sync(null) - } catch (t: Throwable) { - logger.error(t) { "Failed to sync global slash commands" } - } - - commands.keys.filterNotNull().forEach { - try { - sync(it) - } catch (t: Throwable) { - logger.error(t) { "Failed to sync slash commands for guild ID: $it" } - } - } - } - - /** @suppress **/ - public open suspend fun sync(guild: Snowflake?) { - val locale = bot.settings.i18nBuilder.defaultLocale - - val guildObj = if (guild != null) { - val guildObj = kord.getGuild(guild) - - if (guildObj == null) { - logger.warn { "Cannot register slash commands for guild ID $guild, as it seems to be missing." } - return - } - - guildObj - } else { - null - } - - val registered = commands[guild]!! - - val existing = if (guild == null) { - kord.globalCommands.map { Pair(it.name, it.id) }.toList() - } else { - kord.unsafe.guild(guild).commands.map { Pair(it.name, it.id) }.toList() - } - - if (!bot.settings.applicationCommandsBuilder.register) { - registered.forEach { r -> - val existingCommand = existing.firstOrNull { it.first == r.getTranslatedName(locale) } - - if (existingCommand != null) { - commandMap[existingCommand.second] = r - } - } - } else { - val toAdd = registered.filter { r -> existing.all { it.first != r.getTranslatedName(locale) } } - val toUpdate = registered.filter { r -> existing.any { it.first == r.getTranslatedName(locale) } } - val toRemove = existing.filter { e -> registered.all { it.getTranslatedName(locale) != e.first } } - - logger.info { - if (guild == null) { - "Global slash commands: ${toAdd.size} to add / " + - "${toUpdate.size} to update / " + - "${toRemove.size} to remove" - } else { - "Slash commands for guild ${guildObj?.name}: ${toAdd.size} to add / " + - "${toUpdate.size} to update / " + - "${toRemove.size} to remove" - } - } - - val toCreate = toAdd + toUpdate - - if (guild == null) { - val response = kord.createGlobalApplicationCommands { - toCreate.forEach { - val translatedName = it.getTranslatedName(locale) - - logger.debug { "Adding/updating global slash command $translatedName" } - - input( - translatedName, - translationsProvider.translate(it.description, it.extension.bundle, locale = locale) - ) { register(it) } - } - }.toList().associate { it.name to it.id } - - toCreate.forEach { - commandMap[response[it.getTranslatedName(locale)]!!] = it - } - - kord.globalCommands.filter { e -> toRemove.any { it.second == e.id } } - .toList() - .forEach { - logger.debug { "Removing global slash command ${it.name}" } - it.delete() - } - } else { - toCreate.groupBy { it.guildId!! }.forEach { (snowflake, commands) -> - val response = kord.createGuildApplicationCommands(snowflake) { - commands.forEach { - val translatedName = it.getTranslatedName(locale) - - logger.debug { "Adding/updating global slash command $translatedName" } - - input( - translatedName, - translationsProvider.translate(it.description, it.extension.bundle) - ) { register(it) } - } - }.toList().associate { it.name to it.id } - - commands.forEach { - commandMap[response[it.getTranslatedName(locale)]!!] = it - } - } - - kord.unsafe.guild(guild).commands.filter { e -> toRemove.any { it.second == e.id } } - .toList() - .forEach { - logger.debug { "Removing guild slash command ${it.name}" } - it.delete() - } - } - - val commandsWithPerms = commandMap.filterValues { !it.allowByDefault }.toList().groupBy { - it.second.guildId - } - - commandsWithPerms.forEach { (guild, commands) -> - if (guild != null) { - kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guild) { - commands.forEach { (id, commandObj) -> - command(id) { - commandObj.allowedUsers.map { user(it, true) } - commandObj.disallowedUsers.map { user(it, false) } - - commandObj.allowedRoles.map { role(it, true) } - commandObj.disallowedRoles.map { role(it, false) } - } - } - } - } else { - logger.warn { "Applying permissions to global slash commands is currently not supported." } - } - } - } - - logger.info { - if (guild == null) { - "Finished synchronising global slash commands" - } else { - "Finished synchronising slash commands for guild ${guildObj?.name}" - } - } - } - - internal open suspend fun ChatInputCreateBuilder.register(command: SlashCommand<*, *>) { - val locale = bot.settings.i18nBuilder.defaultLocale - - this.defaultPermission = command.guildId == null || command.allowByDefault - - if (command.hasBody) { - val args = command.arguments?.invoke() - - if (args != null) { - args.args.forEach { arg -> - val converter = arg.converter - - if (converter !is SlashCommandConverter) { - error("Argument ${arg.displayName} does not support slash commands.") - } - - if (this.options == null) this.options = mutableListOf() - - // TODO: It's impossible to translate these right now - this.options!! += converter.toSlashOption(arg) - } - } - } else { - command.subCommands.forEach { - val args = it.arguments?.invoke()?.args?.map { arg -> - val converter = arg.converter - - if (converter !is SlashCommandConverter) { - error("Argument ${arg.displayName} does not support slash commands.") - } - - // TODO: It's impossible to translate these right now - converter.toSlashOption(arg) - } - - this.subCommand( - it.name, - translationsProvider.translate(it.description, command.extension.bundle, locale = locale) - ) { - if (args != null) { - if (this.options == null) this.options = mutableListOf() - - this.options!!.addAll(args) - } - } - } - - command.groups.values.forEach { group -> - this.group(group.name, group.description) { - group.subCommands.forEach { - val args = it.arguments?.invoke()?.args?.map { arg -> - val converter = arg.converter - - if (converter !is SlashCommandConverter) { - error("Argument ${arg.displayName} does not support slash commands.") - } - - // TODO: It's impossible to translate these right now - converter.toSlashOption(arg) - } - - this.subCommand( - it.name, - translationsProvider.translate(it.description, command.extension.bundle, locale = locale) - ) { - if (args != null) { - if (this.options == null) this.options = mutableListOf() - - this.options!!.addAll(args) - } - } - } - } - } - } - } - - /** Handle a [ChatInputCommandInteractionCreateEvent] and try to execute the corresponding command. **/ - public open suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { - val commandId = event.interaction.command.rootId - val command = commandMap[commandId] - - if (command == null) { - logger.warn { "Received interaction for unknown slash command: ${commandId.asString}" } - return - } - - if (!command.extension.loaded) { - logger.info { "Ignoring slash command ${command.name} as the extension is unloaded." } - return - } - - command.call(event) - } -} 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 b3133eae4a..21bc9c2906 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 @@ -3,21 +3,22 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry 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 import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry -import com.kotlindiscord.kord.extensions.commands.slash.SlashCommandRegistry import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.events.ExtensionStateEvent import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.event.Event import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import mu.KotlinLogging import org.koin.core.component.KoinComponent @@ -43,7 +44,7 @@ public abstract class Extension : KoinComponent { internal val chatCommandRegistry: ChatCommandRegistry by inject() /** Slash command registry. **/ - internal val slashCommandsRegistry: SlashCommandRegistry by inject() + internal val applicationCommandRegistry: ApplicationCommandRegistry by inject() /** * The name of this extension. @@ -108,13 +109,26 @@ public abstract class Extension : KoinComponent { public open val chatCommandChecks: MutableList> = mutableListOf() + /** + * List of message command checks. + * + * These checks will be checked against all message commands in this extension. + */ + public val messageCommandChecks: MutableList> = mutableListOf() + /** * List of slash command checks. * * These checks will be checked against all slash commands in this extension. */ - public open val slashCommandChecks: MutableList> = - mutableListOf() + public val slashCommandChecks: MutableList> = mutableListOf() + + /** + * List of user command checks. + * + * These checks will be checked against all user commands in this extension. + */ + public val userCommandChecks: MutableList> = mutableListOf() /** String representing the bundle to get translations from for command names/descriptions. **/ public open val bundle: String? = null @@ -207,57 +221,4 @@ public abstract class Extension : KoinComponent { this.setState(ExtensionState.UNLOADED) } - - /** - * Define a check which must pass for the command to be executed. This check will be applied to all - * slash commands in this extension. - * - * A command may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to all slash commands in this extension. - */ - public open fun slashCommandCheck(vararg checks: Check) { - checks.forEach { slashCommandChecks.add(it) } - } - - /** - * Overloaded check function to allow for DSL syntax. - * - * @param check Check to apply to all slash commands in this extension. - */ - @ExtensionDSL - public open fun slashCommandCheck(check: Check) { - slashCommandChecks.add(check) - } - - /** - * Define a check which must pass for the command to be executed. This check will be applied to all commands - * in this extension. - * - * A command may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to all commands in this extension. - */ - @ExtensionDSL - public open fun chatCommandCheck(vararg checks: Check) { - checks.forEach { chatCommandChecks.add(it) } - } - - /** - * Overloaded check function to allow for DSL syntax. - * - * @param check Check to apply to all commands in this extension. - */ - @ExtensionDSL - public open fun chatCommandCheck(check: Check) { - chatCommandChecks.add(check) - } } 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 db3884bcb0..1357b33b1a 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 @@ -5,6 +5,7 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.message.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.application.message.PublicMessageCommand @@ -14,12 +15,39 @@ import com.kotlindiscord.kord.extensions.commands.application.user.EphemeralUser import com.kotlindiscord.kord.extensions.commands.application.user.PublicUserCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.message.MessageCreateEvent import mu.KotlinLogging private val logger = KotlinLogging.logger {} // region: Message commands +/** + * Define a check which must pass for a message command to be executed. This check will be applied to all + * message commands in this extension. + * + * A message command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all slash commands. + */ +public fun Extension.messageCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } +} + +/** + * Overloaded message command check function to allow for DSL syntax. + * + * @param check Check to apply to all slash commands. + */ +public fun Extension.messageCommandCheck(check: Check) { + slashCommandChecks.add(check) +} + /** Register an ephemeral message command, DSL-style. **/ public suspend fun Extension.ephemeralMessageCommand( body: suspend EphemeralMessageCommand.() -> Unit @@ -31,7 +59,7 @@ public suspend fun Extension.ephemeralMessageCommand( } /** Register a custom instance of an ephemeral message command. **/ -public fun Extension.ephemeralMessageCommand( +public suspend fun Extension.ephemeralMessageCommand( commandObj: EphemeralMessageCommand ): EphemeralMessageCommand { try { @@ -43,6 +71,10 @@ public fun Extension.ephemeralMessageCommand( logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } @@ -57,7 +89,7 @@ public suspend fun Extension.publicMessageCommand( } /** Register a custom instance of a public message command. **/ -public fun Extension.publicMessageCommand( +public suspend fun Extension.publicMessageCommand( commandObj: PublicMessageCommand ): PublicMessageCommand { try { @@ -69,11 +101,44 @@ public fun Extension.publicMessageCommand( logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } // endregion +// region: Slash commands (Generic) + +/** + * Define a check which must pass for a slash command to be executed. This check will be applied to all + * slash commands in this extension. + * + * A slash command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all slash commands. + */ +public fun Extension.slashCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } +} + +/** + * Overloaded slash command check function to allow for DSL syntax. + * + * @param check Check to apply to all slash commands. + */ +public fun Extension.slashCommandCheck(check: Check) { + slashCommandChecks.add(check) +} + +// endregion + // region: Slash commands (Ephemeral) /** @@ -101,7 +166,7 @@ public suspend fun Extension.ephemeralSlashCommand( * * @param commandObj EphemeralSlashCommand object to register. */ -public fun Extension.ephemeralSlashCommand( +public suspend fun Extension.ephemeralSlashCommand( commandObj: EphemeralSlashCommand ): EphemeralSlashCommand { try { @@ -113,6 +178,10 @@ public fun Extension.ephemeralSlashCommand( logger.error(e) { "Failed to register subcommand - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } @@ -161,7 +230,7 @@ public suspend fun Extension.publicSlashCommand( * * @param commandObj PublicSlashCommand object to register. */ -public fun Extension.publicSlashCommand( +public suspend fun Extension.publicSlashCommand( commandObj: PublicSlashCommand ): PublicSlashCommand { try { @@ -173,6 +242,10 @@ public fun Extension.publicSlashCommand( logger.error(e) { "Failed to register subcommand - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } @@ -196,6 +269,31 @@ public suspend fun Extension.publicSlashCommand( // region: User commands +/** + * Define a check which must pass for a user command to be executed. This check will be applied to all + * user commands in this extension. + * + * A user command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all slash commands. + */ +public fun Extension.userCommandCheck(vararg checks: Check) { + checks.forEach { slashCommandChecks.add(it) } +} + +/** + * Overloaded user command check function to allow for DSL syntax. + * + * @param check Check to apply to all slash commands. + */ +public fun Extension.userCommandCheck(check: Check) { + slashCommandChecks.add(check) +} + /** Register an ephemeral user command, DSL-style. **/ public suspend fun Extension.ephemeralUserCommand( body: suspend EphemeralUserCommand.() -> Unit @@ -207,7 +305,7 @@ public suspend fun Extension.ephemeralUserCommand( } /** Register a custom instance of an ephemeral user command. **/ -public fun Extension.ephemeralUserCommand( +public suspend fun Extension.ephemeralUserCommand( commandObj: EphemeralUserCommand ): EphemeralUserCommand { try { @@ -219,6 +317,10 @@ public fun Extension.ephemeralUserCommand( logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } @@ -233,7 +335,7 @@ public suspend fun Extension.publicUserCommand( } /** Register a custom instance of a public user command. **/ -public fun Extension.publicUserCommand( +public suspend fun Extension.publicUserCommand( commandObj: PublicUserCommand ): PublicUserCommand { try { @@ -245,6 +347,10 @@ public fun Extension.publicUserCommand( logger.error(e) { "Failed to register message command ${commandObj.name} - $e" } } + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + return commandObj } @@ -252,6 +358,33 @@ public fun Extension.publicUserCommand( // region: Chat commands +/** + * Define a check which must pass for the command to be executed. This check will be applied to all commands + * in this extension. + * + * A command may have multiple checks - all checks must pass for the command to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to all commands in this extension. + */ +@ExtensionDSL +public fun Extension.chatCommandCheck(vararg checks: Check) { + checks.forEach { chatCommandChecks.add(it) } +} + +/** + * Overloaded check function to allow for DSL syntax. + * + * @param check Check to apply to all commands in this extension. + */ +@ExtensionDSL +public fun Extension.chatCommandCheck(check: Check) { + chatCommandChecks.add(check) +} + /** * DSL function for easily registering a command. * diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 06c67e7fd6..23e2d9a5e4 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -25,6 +25,8 @@ suspend fun main() { applicationCommands { enabled = true + + defaultGuild("787452339908116521") } extensions { diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 0f4dd80f43..8133b2864e 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -71,6 +71,34 @@ class TestExtension : Extension() { } override suspend fun setup() { + publicMessageCommand { + name = "Raw Info" + + action { + val message = targetMessages.firstOrNull() ?: return@action + + respond { + content = "**Message command:** Raw content for message sent by ${message.author!!.mention}" + + embed { + description = "```markdown\n${message.content}```" + } + } + } + } + + publicUserCommand { + name = "ping" + + action { + val user = targetUsers.firstOrNull() ?: return@action + + respond { + content = "Let's ping ${user.mention} for no reason. <3" + } + } + } + chatCommand(::ColorArgs) { name = "color" aliases = arrayOf("colour") From ee754171ee4872a6f18ad4345e6ec947d2f3f891 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 1 Sep 2021 15:36:10 +0100 Subject: [PATCH 021/131] Command consistency fixes --- .../kord/extensions/test/bot/Bot.kt | 2 +- .../kord/extensions/ExtensibleBot.kt | 2 +- .../builders/ExtensibleBotBuilder.kt | 46 +++++++++---------- .../extensions/commands/chat/ChatCommand.kt | 6 +-- .../commands/chat/ChatCommandRegistry.kt | 6 +-- .../kord/extensions/extensions/Extension.kt | 18 +++----- .../kord/extensions/extensions/_Commands.kt | 28 ++++++----- .../kord/extensions/test/bot/Bot.kt | 5 +- .../kord/extensions/test/bot/Bot.kt | 2 +- .../kord/extensions/test/bot/Bot.kt | 2 +- 10 files changed, 55 insertions(+), 62 deletions(-) diff --git a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index deeb9c6f8f..860077ad20 100644 --- a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -11,7 +11,7 @@ suspend fun main() { val bot = ExtensibleBot(env("TOKEN")!!) { koinLogLevel = Level.DEBUG - messageCommands { + chatCommands { check(isNotbot) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index bc088778e7..9816739f97 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -169,7 +169,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva logger.info { "Ready!" } } - if (settings.messageCommandsBuilder.enabled) { + if (settings.chatCommandsBuilder.enabled) { on { getKoin().get().handleEvent(this) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 48f4b568c1..181a7302be 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -6,6 +6,9 @@ import com.kotlindiscord.kord.extensions.DISCORD_BLURPLE import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.annotations.BotBuilderDSL import com.kotlindiscord.kord.extensions.checks.types.Check +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.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.extensions.Extension @@ -27,9 +30,6 @@ import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.builder.kord.Shards import dev.kord.core.cache.KordCacheBuilder -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy @@ -78,7 +78,7 @@ public open class ExtensibleBotBuilder { public val membersBuilder: MembersBuilder = MembersBuilder() /** @suppress Builder that shouldn't be set directly by the user. **/ - public val messageCommandsBuilder: ChatCommandsBuilder = ChatCommandsBuilder() + public val chatCommandsBuilder: ChatCommandsBuilder = ChatCommandsBuilder() /** @suppress Builder that shouldn't be set directly by the user. **/ public var presenceBuilder: PresenceBuilder.() -> Unit = { status = PresenceStatus.Online } @@ -131,13 +131,13 @@ public open class ExtensibleBotBuilder { } /** - * DSL function used to configure the bot's message command options. + * DSL function used to configure the bot's chat command options. * * @see ChatCommandsBuilder */ @BotBuilderDSL - public suspend fun messageCommands(builder: suspend ChatCommandsBuilder.() -> Unit) { - builder(messageCommandsBuilder) + public suspend fun chatCommands(builder: suspend ChatCommandsBuilder.() -> Unit) { + builder(chatCommandsBuilder) } /** @@ -227,7 +227,7 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } - loadModule { single { messageCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } + loadModule { single { chatCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } loadModule { single { @@ -790,7 +790,7 @@ public open class ExtensibleBotBuilder { } } - /** Builder used for configuring the bot's message command options. **/ + /** Builder used for configuring the bot's chat command options. **/ @BotBuilderDSL public class ChatCommandsBuilder { /** Whether to invoke commands on bot mentions, in addition to using chat prefixes. Defaults to `true`. **/ @@ -822,7 +822,7 @@ public open class ExtensibleBotBuilder { * Register a lambda that takes a [MessageCreateEvent] object and the default prefix, and returns the * command prefix to be made use of for that message event. * - * This is intended to allow for different message command prefixes in different contexts - for example, + * This is intended to allow for different chat command prefixes in different contexts - for example, * guild-specific prefixes. */ public fun prefix(builder: suspend (MessageCreateEvent).(String) -> String) { @@ -883,21 +883,21 @@ public open class ExtensibleBotBuilder { * * These checks will be checked against all message commands. */ - public val messageCommandChecks: MutableList> = mutableListOf() + public val messageCommandChecks: MutableList = mutableListOf() /** * List of slash command checks. * * These checks will be checked against all slash commands. */ - public val slashCommandChecks: MutableList> = mutableListOf() + public val slashCommandChecks: MutableList = mutableListOf() /** * List of user command checks. * * These checks will be checked against all user commands. */ - public val userCommandChecks: MutableList> = mutableListOf() + public val userCommandChecks: MutableList = mutableListOf() /** Set a guild ID to use for all global application commands. Intended for testing. **/ public fun defaultGuild(id: Snowflake) { @@ -934,8 +934,8 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun messageCommandCheck(vararg checks: Check) { - checks.forEach { slashCommandChecks.add(it) } + public fun messageCommandCheck(vararg checks: MessageCommandCheck) { + checks.forEach { messageCommandChecks.add(it) } } /** @@ -943,8 +943,8 @@ public open class ExtensibleBotBuilder { * * @param check Check to apply to all slash commands. */ - public fun messageCommandCheck(check: Check) { - slashCommandChecks.add(check) + public fun messageCommandCheck(check: MessageCommandCheck) { + messageCommandChecks.add(check) } /** @@ -959,7 +959,7 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun slashCommandCheck(vararg checks: Check) { + public fun slashCommandCheck(vararg checks: SlashCommandCheck) { checks.forEach { slashCommandChecks.add(it) } } @@ -968,7 +968,7 @@ public open class ExtensibleBotBuilder { * * @param check Check to apply to all slash commands. */ - public fun slashCommandCheck(check: Check) { + public fun slashCommandCheck(check: SlashCommandCheck) { slashCommandChecks.add(check) } @@ -984,8 +984,8 @@ public open class ExtensibleBotBuilder { * * @param checks Checks to apply to all slash commands. */ - public fun userCommandCheck(vararg checks: Check) { - checks.forEach { slashCommandChecks.add(it) } + public fun userCommandCheck(vararg checks: UserCommandCheck) { + checks.forEach { userCommandChecks.add(it) } } /** @@ -993,8 +993,8 @@ public open class ExtensibleBotBuilder { * * @param check Check to apply to all slash commands. */ - public fun userCommandCheck(check: Check) { - slashCommandChecks.add(check) + public fun userCommandCheck(check: UserCommandCheck) { + userCommandChecks.add(check) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 17a5c6ab92..dd0ab76356 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -35,10 +35,10 @@ import java.util.* private val logger = KotlinLogging.logger {} /** - * Class representing a message command. + * Class representing a chat command. * * You shouldn't need to use this class directly - instead, create an [Extension] and use the - * [command function][Extension.messageContentCommand] to register your command, by overriding the [Extension.setup] + * `chatCommand` function to register your command, by overriding the [Extension.setup] * function. * * @param extension The [Extension] that registered this command. @@ -294,7 +294,7 @@ public open class ChatCommand( val locale = event.getLocale() // global command checks - for (check in extension.bot.settings.messageCommandsBuilder.checkList) { + for (check in extension.bot.settings.chatCommandsBuilder.checkList) { val context = CheckContext(event, locale) check(context) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt index 96838bd040..7ff3d5b3ac 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt @@ -42,7 +42,7 @@ public open class ChatCommandRegistry : KoinComponent { /** @suppress **/ public open val commandThreadPool: ExecutorCoroutineDispatcher by lazy { Executors - .newFixedThreadPool(botSettings.messageCommandsBuilder.threads) + .newFixedThreadPool(botSettings.chatCommandsBuilder.threads) .asCoroutineDispatcher() } @@ -107,7 +107,7 @@ public open class ChatCommandRegistry : KoinComponent { * needed. */ public open suspend fun getPrefix(event: MessageCreateEvent): String = - botSettings.messageCommandsBuilder.prefixCallback(event, botSettings.messageCommandsBuilder.defaultPrefix) + botSettings.chatCommandsBuilder.prefixCallback(event, botSettings.chatCommandsBuilder.defaultPrefix) /** * Check whether the given string starts with a mention referring to the bot. If so, the matching mention string @@ -143,7 +143,7 @@ public open class ChatCommandRegistry : KoinComponent { content = when { // Starts with the right mention and mentions are allowed, so remove it - mention != null && botSettings.messageCommandsBuilder.invokeOnMention -> content.substring(mention.length) + mention != null && botSettings.chatCommandsBuilder.invokeOnMention -> content.substring(mention.length) // Starts with the right prefix, so remove it content.startsWith(prefix) -> content.substring(prefix.length) 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 21bc9c2906..b7e26f2c78 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 @@ -3,7 +3,7 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.* import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand @@ -16,10 +16,6 @@ import com.kotlindiscord.kord.extensions.events.ExtensionStateEvent import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.event.Event -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent -import dev.kord.core.event.message.MessageCreateEvent import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -102,11 +98,11 @@ public abstract class Extension : KoinComponent { public open val userCommands: MutableList> = mutableListOf() /** - * List of message command checks. + * List of chat command checks. * - * These checks will be checked against all commands in this extension. + * These checks will be checked against all chat commands in this extension. */ - public open val chatCommandChecks: MutableList> = + public open val chatCommandChecks: MutableList = mutableListOf() /** @@ -114,21 +110,21 @@ public abstract class Extension : KoinComponent { * * These checks will be checked against all message commands in this extension. */ - public val messageCommandChecks: MutableList> = mutableListOf() + public val messageCommandChecks: MutableList = mutableListOf() /** * List of slash command checks. * * These checks will be checked against all slash commands in this extension. */ - public val slashCommandChecks: MutableList> = mutableListOf() + public val slashCommandChecks: MutableList = mutableListOf() /** * List of user command checks. * * These checks will be checked against all user commands in this extension. */ - public val userCommandChecks: MutableList> = mutableListOf() + public val userCommandChecks: MutableList = mutableListOf() /** String representing the bundle to get translations from for command names/descriptions. **/ public open val bundle: String? = null 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 1357b33b1a..072968d769 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 @@ -5,7 +5,7 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.* import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.message.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.application.message.PublicMessageCommand @@ -15,8 +15,6 @@ import com.kotlindiscord.kord.extensions.commands.application.user.EphemeralUser import com.kotlindiscord.kord.extensions.commands.application.user.PublicUserCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.core.event.message.MessageCreateEvent import mu.KotlinLogging private val logger = KotlinLogging.logger {} @@ -35,8 +33,8 @@ private val logger = KotlinLogging.logger {} * * @param checks Checks to apply to all slash commands. */ -public fun Extension.messageCommandCheck(vararg checks: Check) { - checks.forEach { slashCommandChecks.add(it) } +public fun Extension.messageCommandCheck(vararg checks: MessageCommandCheck) { + checks.forEach { messageCommandChecks.add(it) } } /** @@ -44,8 +42,8 @@ public fun Extension.messageCommandCheck(vararg checks: Check) { - slashCommandChecks.add(check) +public fun Extension.messageCommandCheck(check: MessageCommandCheck) { + messageCommandChecks.add(check) } /** Register an ephemeral message command, DSL-style. **/ @@ -124,7 +122,7 @@ public suspend fun Extension.publicMessageCommand( * * @param checks Checks to apply to all slash commands. */ -public fun Extension.slashCommandCheck(vararg checks: Check) { +public fun Extension.slashCommandCheck(vararg checks: SlashCommandCheck) { checks.forEach { slashCommandChecks.add(it) } } @@ -133,7 +131,7 @@ public fun Extension.slashCommandCheck(vararg checks: Check) { +public fun Extension.slashCommandCheck(check: SlashCommandCheck) { slashCommandChecks.add(check) } @@ -281,8 +279,8 @@ public suspend fun Extension.publicSlashCommand( * * @param checks Checks to apply to all slash commands. */ -public fun Extension.userCommandCheck(vararg checks: Check) { - checks.forEach { slashCommandChecks.add(it) } +public fun Extension.userCommandCheck(vararg checks: UserCommandCheck) { + checks.forEach { userCommandChecks.add(it) } } /** @@ -290,8 +288,8 @@ public fun Extension.userCommandCheck(vararg checks: Check) { - slashCommandChecks.add(check) +public fun Extension.userCommandCheck(check: UserCommandCheck) { + userCommandChecks.add(check) } /** Register an ephemeral user command, DSL-style. **/ @@ -371,7 +369,7 @@ public suspend fun Extension.publicUserCommand( * @param checks Checks to apply to all commands in this extension. */ @ExtensionDSL -public fun Extension.chatCommandCheck(vararg checks: Check) { +public fun Extension.chatCommandCheck(vararg checks: ChatCommandCheck) { checks.forEach { chatCommandChecks.add(it) } } @@ -381,7 +379,7 @@ public fun Extension.chatCommandCheck(vararg checks: Check) * @param check Check to apply to all commands in this extension. */ @ExtensionDSL -public fun Extension.chatCommandCheck(check: Check) { +public fun Extension.chatCommandCheck(check: ChatCommandCheck) { chatCommandChecks.add(check) } diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 23e2d9a5e4..92ebc8ca96 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -9,8 +9,9 @@ suspend fun main() { val bot = ExtensibleBot(env("TOKEN")!!) { koinLogLevel = Level.DEBUG - messageCommands { + chatCommands { defaultPrefix = "?" + enabled = true check(isNotbot) @@ -24,8 +25,6 @@ suspend fun main() { } applicationCommands { - enabled = true - defaultGuild("787452339908116521") } diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index d5b3f137a1..5f3bc6a70a 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -24,7 +24,7 @@ suspend fun main() { } } - messageCommands { + chatCommands { defaultPrefix = "?" prefix { default -> diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index f5e9eec545..5716acf6c0 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -8,7 +8,7 @@ suspend fun main() { val bot = ExtensibleBot(env("TOKEN")!!) { koinLogLevel = Level.DEBUG - messageCommands { + chatCommands { defaultPrefix = "?" prefix { default -> From 9c5e6bd2a81b846d17c17b43d8f373901130d50b Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 2 Sep 2021 15:35:34 +0100 Subject: [PATCH 022/131] Small cleanups and additional errors --- .../EphemeralApplicationCommandContext.kt | 8 +++++++- .../PublicApplicationCommandContext.kt | 8 +++++++- .../kord/extensions/extensions/_Commands.kt | 20 ++++++++++++++++++- .../kord/extensions/test/bot/TestExtension.kt | 12 +++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt index 63425df906..98dabe1ea7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt @@ -27,4 +27,10 @@ public suspend inline fun EphemeralApplicationCommandContext.respond( */ public suspend inline fun EphemeralApplicationCommandContext.edit( builder: EphemeralInteractionResponseModifyBuilder.() -> Unit -): Unit? = (interactionResponse as? EphemeralInteractionResponseBehavior)?.edit(builder) +) { + val response = interactionResponse as? EphemeralInteractionResponseBehavior ?: error( + "Unable to edit: No `initialResponse` builder was provided." + ) + + response.edit(builder) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt index 8d15d1555e..9cbc9abcc4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt @@ -24,4 +24,10 @@ public suspend inline fun PublicApplicationCommandContext.respond( */ public suspend inline fun PublicApplicationCommandContext.edit( builder: PublicInteractionResponseModifyBuilder.() -> Unit -): Message? = (interactionResponse as? PublicInteractionResponseBehavior)?.edit(builder) +): Message { + val response = interactionResponse as? PublicInteractionResponseBehavior ?: error( + "Unable to edit: No `initialResponse` builder was provided." + ) + + return response.edit(builder) +} 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 072968d769..3b2e826d17 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 @@ -5,7 +5,10 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.CommandRegistrationException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.checks.types.* +import com.kotlindiscord.kord.extensions.checks.types.ChatCommandCheck +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.message.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.application.message.PublicMessageCommand @@ -47,6 +50,7 @@ public fun Extension.messageCommandCheck(check: MessageCommandCheck) { } /** Register an ephemeral message command, DSL-style. **/ +@ExtensionDSL public suspend fun Extension.ephemeralMessageCommand( body: suspend EphemeralMessageCommand.() -> Unit ): EphemeralMessageCommand { @@ -57,6 +61,7 @@ public suspend fun Extension.ephemeralMessageCommand( } /** Register a custom instance of an ephemeral message command. **/ +@ExtensionDSL public suspend fun Extension.ephemeralMessageCommand( commandObj: EphemeralMessageCommand ): EphemeralMessageCommand { @@ -77,6 +82,7 @@ public suspend fun Extension.ephemeralMessageCommand( } /** Register a public message command, DSL-style. **/ +@ExtensionDSL public suspend fun Extension.publicMessageCommand( body: suspend PublicMessageCommand.() -> Unit ): PublicMessageCommand { @@ -87,6 +93,7 @@ public suspend fun Extension.publicMessageCommand( } /** Register a custom instance of a public message command. **/ +@ExtensionDSL public suspend fun Extension.publicMessageCommand( commandObj: PublicMessageCommand ): PublicMessageCommand { @@ -147,6 +154,7 @@ public fun Extension.slashCommandCheck(check: SlashCommandCheck) { * @param arguments Arguments builder (probably a reference to the class constructor). * @param body Builder lambda used for setting up the slash command object. */ +@ExtensionDSL public suspend fun Extension.ephemeralSlashCommand( arguments: () -> T, body: suspend EphemeralSlashCommand.() -> Unit @@ -164,6 +172,7 @@ public suspend fun Extension.ephemeralSlashCommand( * * @param commandObj EphemeralSlashCommand object to register. */ +@ExtensionDSL public suspend fun Extension.ephemeralSlashCommand( commandObj: EphemeralSlashCommand ): EphemeralSlashCommand { @@ -190,6 +199,7 @@ public suspend fun Extension.ephemeralSlashCommand( * * @param body Builder lambda used for setting up the slash command object. */ +@ExtensionDSL public suspend fun Extension.ephemeralSlashCommand( body: suspend EphemeralSlashCommand.() -> Unit ): EphemeralSlashCommand { @@ -211,6 +221,7 @@ public suspend fun Extension.ephemeralSlashCommand( * @param arguments Arguments builder (probably a reference to the class constructor). * @param body Builder lambda used for setting up the slash command object. */ +@ExtensionDSL public suspend fun Extension.publicSlashCommand( arguments: () -> T, body: suspend PublicSlashCommand.() -> Unit @@ -228,6 +239,7 @@ public suspend fun Extension.publicSlashCommand( * * @param commandObj PublicSlashCommand object to register. */ +@ExtensionDSL public suspend fun Extension.publicSlashCommand( commandObj: PublicSlashCommand ): PublicSlashCommand { @@ -254,6 +266,7 @@ public suspend fun Extension.publicSlashCommand( * * @param body Builder lambda used for setting up the slash command object. */ +@ExtensionDSL public suspend fun Extension.publicSlashCommand( body: suspend PublicSlashCommand.() -> Unit ): PublicSlashCommand { @@ -293,6 +306,7 @@ public fun Extension.userCommandCheck(check: UserCommandCheck) { } /** Register an ephemeral user command, DSL-style. **/ +@ExtensionDSL public suspend fun Extension.ephemeralUserCommand( body: suspend EphemeralUserCommand.() -> Unit ): EphemeralUserCommand { @@ -303,6 +317,7 @@ public suspend fun Extension.ephemeralUserCommand( } /** Register a custom instance of an ephemeral user command. **/ +@ExtensionDSL public suspend fun Extension.ephemeralUserCommand( commandObj: EphemeralUserCommand ): EphemeralUserCommand { @@ -323,6 +338,7 @@ public suspend fun Extension.ephemeralUserCommand( } /** Register a public user command, DSL-style. **/ +@ExtensionDSL public suspend fun Extension.publicUserCommand( body: suspend PublicUserCommand.() -> Unit ): PublicUserCommand { @@ -333,6 +349,7 @@ public suspend fun Extension.publicUserCommand( } /** Register a custom instance of a public user command. **/ +@ExtensionDSL public suspend fun Extension.publicUserCommand( commandObj: PublicUserCommand ): PublicUserCommand { @@ -425,6 +442,7 @@ public suspend fun Extension.chatCommand( * * @param commandObj MessageContentCommand object to register. */ +@ExtensionDSL public fun Extension.chatCommand( commandObj: ChatCommand ): ChatCommand { diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 8133b2864e..0fcd523aca 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -74,6 +74,12 @@ class TestExtension : Extension() { publicMessageCommand { name = "Raw Info" + check { + failIf("This message command only supports non-webhook, non-interaction messages.") { + event.interaction.messages?.values?.firstOrNull()?.author == null + } + } + action { val message = targetMessages.firstOrNull() ?: return@action @@ -90,6 +96,12 @@ class TestExtension : Extension() { publicUserCommand { name = "ping" + check { + failIf("That's me, you can't make me ping myself!") { + event.interaction.users?.values?.firstOrNull()?.id == kord.selfId + } + } + action { val user = targetUsers.firstOrNull() ?: return@action From 297f62f803110533076173f6e930d65caee2a730 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 6 Sep 2021 19:09:15 +0100 Subject: [PATCH 023/131] [skip ci] Working components. Needs doc comments. --- .../kord/extensions/ExtensibleBot.kt | 13 +- .../builders/ExtensibleBotBuilder.kt | 30 ++ .../kord/extensions/commands/Command.kt | 4 +- .../application/ApplicationCommand.kt | 3 - .../PublicApplicationCommandContext.kt | 33 -- .../message/EphemeralMessageCommand.kt | 2 +- .../message/EphemeralMessageCommandContext.kt | 4 +- .../message/PublicMessageCommand.kt | 2 +- .../message/PublicMessageCommandContext.kt | 4 +- .../slash/EphemeralSlashCommand.kt | 2 +- .../slash/EphemeralSlashCommandContext.kt | 6 +- .../application/slash/PublicSlashCommand.kt | 2 +- .../slash/PublicSlashCommandContext.kt | 4 +- .../application/user/EphemeralUserCommand.kt | 2 +- .../user/EphemeralUserCommandContext.kt | 4 +- .../application/user/PublicUserCommand.kt | 2 +- .../user/PublicUserCommandContext.kt | 4 +- .../commands/chat/ChatCommandContext.kt | 45 +-- .../kord/extensions/components/AutoAckType.kt | 13 - .../kord/extensions/components/Component.kt | 40 +++ .../components/ComponentContainer.kt | 193 ++++++++++++ .../extensions/components/ComponentContext.kt | 134 ++++++++ .../components/ComponentRegistry.kt | 56 ++++ .../components/ComponentWithAction.kt | 125 ++++++++ .../extensions/components/ComponentWithID.kt | 13 + .../kord/extensions/components/Components.kt | 296 ------------------ .../kord/extensions/components/_Functions.kt | 110 +++++++ .../builders/ActionableComponentBuilder.kt | 279 ----------------- .../components/builders/ButtonBuilder.kt | 69 ---- .../components/builders/ComponentBuilder.kt | 51 --- .../builders/DisabledButtonBuilder.kt | 46 --- .../builders/InteractiveButtonBuilder.kt | 64 ---- .../components/builders/LinkButtonBuilder.kt | 39 --- .../components/builders/MenuBuilder.kt | 103 ------ .../buttons/DisabledInteractionButton.kt | 25 ++ .../buttons/EphemeralInteractionButton.kt | 73 +++++ .../EphemeralInteractionButtonContext.kt | 11 + .../components/buttons/InteractionButton.kt | 16 + .../buttons/InteractionButtonContext.kt | 10 + .../buttons/InteractionButtonWithAction.kt | 106 +++++++ .../buttons/InteractionButtonWithID.kt | 18 ++ .../buttons/LinkInteractionButton.kt | 22 ++ .../buttons/PublicInteractionButton.kt | 73 +++++ .../buttons/PublicInteractionButtonContext.kt | 11 + .../contexts/ActionableComponentContext.kt | 216 ------------- .../contexts/InteractiveButtonContext.kt | 27 -- .../components/contexts/MenuContext.kt | 31 -- .../components/menus/EphemeralSelectMenu.kt | 55 ++++ .../menus/EphemeralSelectMenuContext.kt | 12 + .../components/menus/PublicSelectMenu.kt | 55 ++++ .../menus/PublicSelectMenuContext.kt | 12 + .../extensions/components/menus/SelectMenu.kt | 173 ++++++++++ .../components/menus/SelectMenuContext.kt | 12 + .../components/types/HasPartialEmoji.kt | 53 ++++ .../extensions/impl/HelpExtension.kt | 2 - .../extensions/impl/SentryExtension.kt | 2 +- .../EphemeralInteractionContext.kt} | 29 +- .../interactions/PublicInteractionContext.kt | 55 ++++ .../pagination/BaseButtonPaginator.kt | 112 ++++--- .../extensions/pagination/BasePaginator.kt | 3 - .../pagination/EphemeralResponsePaginator.kt | 81 +++++ .../pagination/MessageButtonPaginator.kt | 17 +- ...aginator.kt => PublicFollowUpPaginator.kt} | 67 ++-- .../pagination/PublicResponsePaginator.kt | 83 +++++ .../kord/extensions/pagination/_Functions.kt | 42 +++ .../pagination/builders/PaginatorBuilder.kt | 2 - .../kord/extensions/utils/scheduling/Task.kt | 18 +- .../kord/extensions/test/bot/TestExtension.kt | 158 +++++----- 68 files changed, 1926 insertions(+), 1553 deletions(-) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithID.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ButtonBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/DisabledButtonBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/LinkButtonBuilder.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/ActionableComponentContext.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/{commands/application/EphemeralApplicationCommandContext.kt => interactions/EphemeralInteractionContext.kt} (53%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/{InteractionButtonPaginator.kt => PublicFollowUpPaginator.kt} (51%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 9816739f97..c95478da77 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry +import com.kotlindiscord.kord.extensions.components.ComponentRegistry import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.events.ExtensionEvent import com.kotlindiscord.kord.extensions.extensions.Extension @@ -20,9 +21,7 @@ import dev.kord.core.event.Event import dev.kord.core.event.gateway.DisconnectEvent import dev.kord.core.event.gateway.ReadyEvent import dev.kord.core.event.guild.GuildCreateEvent -import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import dev.kord.core.event.interaction.* import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.on import dev.kord.gateway.Intents @@ -169,6 +168,14 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva logger.info { "Ready!" } } + on { + getKoin().get().handle(this) + } + + on { + getKoin().get().handle(this) + } + if (settings.chatCommandsBuilder.enabled) { on { getKoin().get().handleEvent(this) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 181a7302be..c0a2ce293d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -11,6 +11,7 @@ import com.kotlindiscord.kord.extensions.checks.types.SlashCommandCheck import com.kotlindiscord.kord.extensions.checks.types.UserCommandCheck import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry +import com.kotlindiscord.kord.extensions.components.ComponentRegistry import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations import com.kotlindiscord.kord.extensions.i18n.SupportedLocales @@ -62,6 +63,9 @@ public open class ExtensibleBotBuilder { /** @suppress Builder that shouldn't be set directly by the user. **/ public val cacheBuilder: CacheBuilder = CacheBuilder() + /** @suppress Builder that shouldn't be set directly by the user. **/ + public val componentsBuilder: ComponentsBuilder = ComponentsBuilder() + /** @suppress Builder that shouldn't be set directly by the user. **/ public open val extensionsBuilder: ExtensionsBuilder = ExtensionsBuilder() @@ -105,6 +109,16 @@ public open class ExtensibleBotBuilder { builder(cacheBuilder) } + /** + * DSL function used to configure the bot's components system. + * + * @see ComponentsBuilder + */ + @BotBuilderDSL + public suspend fun components(builder: suspend ComponentsBuilder.() -> Unit) { + builder(componentsBuilder) + } + /** * DSL function used to insert code at various points in the bot's lifecycle. * @@ -228,6 +242,7 @@ public open class ExtensibleBotBuilder { loadModule { single { this@ExtensibleBotBuilder } bind ExtensibleBotBuilder::class } loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } loadModule { single { chatCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } + loadModule { single { componentsBuilder.registryBuilder() } bind ComponentRegistry::class } loadModule { single { @@ -313,6 +328,21 @@ public open class ExtensibleBotBuilder { } } + /** Builder used to configure the bot's components settings. **/ + @BotBuilderDSL + public class ComponentsBuilder { + /** @suppress Component registry builder. **/ + public var registryBuilder: () -> ComponentRegistry = ::ComponentRegistry + + /** + * Register a builder (usually a constructor) returning a [ComponentRegistry] instance, which may be useful + * if you need to register a custom subclass. + */ + public fun registry(builder: () -> ComponentRegistry) { + registryBuilder = builder + } + } + /** Builder used for configuring the bot's extension options, and registering custom extensions. **/ @BotBuilderDSL public open class ExtensionsBuilder { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt index 25c34c0c85..e8a4c394af 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt @@ -21,9 +21,9 @@ public abstract class Command(public val extension: Extension) { public open lateinit var name: String /** - * An internal function used to ensure that all of a command's required arguments are present. + * An internal function used to ensure that all of a command's required arguments are present and correct. * - * @throws InvalidCommandException Thrown when a required argument hasn't been set. + * @throws InvalidCommandException Thrown when a required argument hasn't been set or is invalid. */ @Throws(InvalidCommandException::class) public open fun validate() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index c56f2199ef..56eb06319d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -202,9 +202,6 @@ public abstract class ApplicationCommand( public open suspend fun runStandardChecks(event: E): Boolean { val locale = event.getLocale() - // TODO: Global checks - // TODO: Extension-level checks - checkList.forEach { check -> val context = CheckContext(event, locale) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt deleted file mode 100644 index 9cbc9abcc4..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/PublicApplicationCommandContext.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.kotlindiscord.kord.extensions.commands.application - -import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior -import dev.kord.core.behavior.interaction.edit -import dev.kord.core.behavior.interaction.followUp -import dev.kord.core.entity.Message -import dev.kord.core.entity.interaction.PublicFollowupMessage -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder - -/** Interface representing a public-only application command context. **/ -public interface PublicApplicationCommandContext { - /** Response created by acknowledging the interaction publicly. **/ - public val interactionResponse: PublicInteractionResponseBehavior -} - -/** Respond to the current interaction with a public followup. **/ -public suspend inline fun PublicApplicationCommandContext.respond( - builder: PublicFollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage = interactionResponse.followUp(builder) - -/** - * Edit the current interaction's response, if one was sent via `initialResponse`. - */ -public suspend inline fun PublicApplicationCommandContext.edit( - builder: PublicInteractionResponseModifyBuilder.() -> Unit -): Message { - val response = interactionResponse as? PublicInteractionResponseBehavior ?: error( - "Unable to edit: No `initialResponse` builder was provided." - ) - - return response.edit(builder) -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index c14f4a09d4..d0faa0c2a3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.commands.application.message import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt index 106a070e8f..cbd0414e57 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent @@ -9,4 +9,4 @@ public class EphemeralMessageCommandContext( override val event: MessageCommandInteractionCreateEvent, override val command: MessageCommand, override val interactionResponse: EphemeralInteractionResponseBehavior -) : MessageCommandContext(event, command), EphemeralApplicationCommandContext +) : MessageCommandContext(event, command), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 2d074ae9db..ff313e64cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.commands.application.message import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt index b5f6beef1c..6e81775764 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent @@ -9,4 +9,4 @@ public class PublicMessageCommandContext( override val event: MessageCommandInteractionCreateEvent, override val command: MessageCommand, override val interactionResponse: PublicInteractionResponseBehavior -) : MessageCommandContext(event, command), PublicApplicationCommandContext +) : MessageCommandContext(event, command), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index fcb1a62147..572598176d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -4,8 +4,8 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt index 8da3efe7ce..c180f2d0a4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt @@ -1,13 +1,13 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent /** Ephemeral-only slash command context. **/ -public class EphemeralSlashCommandContext ( +public class EphemeralSlashCommandContext( override val event: ChatInputCommandInteractionCreateEvent, override val command: SlashCommand, A>, override val interactionResponse: EphemeralInteractionResponseBehavior -) : SlashCommandContext, A>(event, command), EphemeralApplicationCommandContext +) : SlashCommandContext, A>(event, command), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 176b40a7bd..221edd7de5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -4,8 +4,8 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt index 7f1f377b3f..43fd3766a8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent @@ -10,4 +10,4 @@ public class PublicSlashCommandContext( override val event: ChatInputCommandInteractionCreateEvent, override val command: SlashCommand, A>, override val interactionResponse: PublicInteractionResponseBehavior -) : SlashCommandContext, A>(event, command), PublicApplicationCommandContext +) : SlashCommandContext, A>(event, command), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 101d7f43fa..422d5b3239 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt index bdbf150a9a..28525e2104 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.commands.application.EphemeralApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent @@ -9,4 +9,4 @@ public class EphemeralUserCommandContext( override val event: UserCommandInteractionCreateEvent, override val command: UserCommand, override val interactionResponse: EphemeralInteractionResponseBehavior -) : UserCommandContext(event, command), EphemeralApplicationCommandContext +) : UserCommandContext(event, command), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index dbad6f4a58..40a496c547 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -3,8 +3,8 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt index 42cde6fcb0..04d41d6250 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.commands.application.PublicApplicationCommandContext +import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent @@ -9,4 +9,4 @@ public class PublicUserCommandContext( override val event: UserCommandInteractionCreateEvent, override val command: UserCommand, override val interactionResponse: PublicInteractionResponseBehavior -) : UserCommandContext(event, command), PublicApplicationCommandContext +) : UserCommandContext(event, command), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index 2435dfee2b..ba2af247cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -5,7 +5,6 @@ package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext -import com.kotlindiscord.kord.extensions.components.Components import com.kotlindiscord.kord.extensions.extensions.base.HelpProvider import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder @@ -18,8 +17,6 @@ import dev.kord.core.entity.Member import dev.kord.core.entity.Message import dev.kord.core.entity.User import dev.kord.core.event.message.MessageCreateEvent -import dev.kord.rest.builder.message.create.MessageCreateBuilder -import dev.kord.rest.builder.message.modify.MessageModifyBuilder /** * Command context object representing the context given to chat commands. @@ -92,7 +89,7 @@ public open class ChatCommandContext( body: suspend PaginatorBuilder.() -> Unit ): MessageButtonPaginator { - val builder = PaginatorBuilder(command.extension, getLocale(), defaultGroup = defaultGroup) + val builder = PaginatorBuilder(getLocale(), defaultGroup = defaultGroup) body(builder) @@ -122,44 +119,4 @@ public open class ChatCommandContext( replacements: Array = arrayOf(), useReply: Boolean = true ): Message = respond(translate(key, replacements), useReply) - - /** - * Convenience function for adding components to your message via the [Components] class. - * - * @see Components - */ - public suspend fun MessageCreateBuilder.components( - timeoutSeconds: Long? = null, - body: suspend Components.() -> Unit - ): Components { - val components = Components(command.extension) - - body(components) - - with(components) { - setup(timeoutSeconds) - } - - return components - } - - /** - * Convenience function for adding components to your message via the [Components] class. - * - * @see Components - */ - public suspend fun MessageModifyBuilder.components( - timeoutSeconds: Long? = null, - body: suspend Components.() -> Unit - ): Components { - val components = Components(command.extension) - - body(components) - - with(components) { - setup(timeoutSeconds) - } - - return components - } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt deleted file mode 100644 index 4de39915c7..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/AutoAckType.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.kotlindiscord.kord.extensions.components - -/** Acknowledgement type for autoAck. **/ -public sealed class AutoAckType { - /** Ephemeral acknowledgement. **/ - public object EPHEMERAL : AutoAckType() - - /** Public acknowledgement. **/ - public object PUBLIC : AutoAckType() - - /** Do not ack automatically. **/ - public object NONE : AutoAckType() -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt new file mode 100644 index 0000000000..752496eb54 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt @@ -0,0 +1,40 @@ +package com.kotlindiscord.kord.extensions.components + +import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import dev.kord.core.Kord +import dev.kord.rest.builder.component.ActionRowBuilder +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public abstract class Component : KoinComponent { + /** Component width, how many "slots" in one row it needs to be added to the row. **/ + public open val unitWidth: Int = 1 + + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + + /** Quick access to the command registry. **/ + public val registry: ApplicationCommandRegistry by inject() + + /** Bot settings object. **/ + public val settings: ExtensibleBotBuilder by inject() + + /** Bot object. **/ + public val bot: ExtensibleBot by inject() + + /** Kord instance, backing the ExtensibleBot. **/ + public val kord: Kord by inject() + + /** Sentry adapter, for easy access to Sentry functions. **/ + public val sentry: SentryAdapter by inject() + + public open var bundle: String? = null + + public abstract fun validate() + + public abstract fun apply(builder: ActionRowBuilder) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt new file mode 100644 index 0000000000..252855582a --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt @@ -0,0 +1,193 @@ +package com.kotlindiscord.kord.extensions.components + +import dev.kord.rest.builder.message.create.MessageCreateBuilder +import dev.kord.rest.builder.message.create.actionRow +import dev.kord.rest.builder.message.modify.MessageModifyBuilder +import dev.kord.rest.builder.message.modify.actionRow +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public const val ROW_SIZE: Int = 5 + +public open class ComponentContainer : KoinComponent { + internal val registry: ComponentRegistry by inject() + + public open val unsortedComponents: MutableList = mutableListOf() + + public open val rows: Array> = arrayOf( + // Up to 5 rows of components + + mutableListOf(), + mutableListOf(), + mutableListOf(), + mutableListOf(), + mutableListOf(), + ) + + public open fun removeAll() { + rows.toList().flatten().forEach { component -> + if (component is ComponentWithID) { + registry.unregister(component) + } + } + + rows.forEach { it.clear() } + } + + public open fun remove(component: Component): Boolean { + if (rows.any { it.remove(component) }) { + if (component is ComponentWithID) { + registry.unregister(component) + } + + return true + } + + return false + } + + public open fun replace(old: Component, new: ComponentWithID): Boolean { + for (row in rows) { + val index = row.indexOf(old) + + if (index == -1) { + continue + } + + val freeSlots = (ROW_SIZE - row.size) + old.unitWidth + + if (new.unitWidth > freeSlots) { + error( + "The given component takes up ${old.unitWidth} slot/s, but its row will only have " + + "$freeSlots available slots remaining." + ) + } + + row[index] = new + registry.register(new) + + return true + } + + return false + } + + public open fun replace(id: String, new: ComponentWithID): Boolean { + for (row in rows) { + val index = row.indexOfFirst { it is ComponentWithID && it.id == id } + + if (index == -1) { + continue + } + + val old = row[index] + val freeSlots = (ROW_SIZE - row.size) + old.unitWidth + + if (new.unitWidth > freeSlots) { + error( + "The given component takes up ${old.unitWidth} slots, but its row will only have " + + "$freeSlots available slots remaining." + ) + } + + row[index] = new + registry.register(new) + + return true + } + + return false + } + + public open fun add(component: Component, rowNum: Int? = null) { + component.validate() + + if (rowNum == null) { + unsortedComponents.add(component) + + return + } + + if (rowNum < 0 || rowNum >= rows.size) { + error("The given row number ($rowNum) must be between 0 to ${rows.size - 1}, inclusive.") + } + + val row = rows[rowNum] + + if (row.size >= ROW_SIZE) { + error( + "Row $rowNum is full, no more components can be added to it." + ) + } + + if (row.size + component.unitWidth > ROW_SIZE) { + error( + "The given component takes up ${component.unitWidth} slots, but row $rowNum only has " + + "${ROW_SIZE - row.size} available slots remaining." + ) + } + + row.add(component) + + if (component is ComponentWithID) { + registry.register(component) + } + } + + public open fun sort() { + while (unsortedComponents.isNotEmpty()) { + val component = unsortedComponents.removeFirst() + var sorted = false + + for (row in rows) { + if (row.size >= ROW_SIZE || row.size + component.unitWidth > ROW_SIZE) { + continue + } + + row.add(component) + sorted = true + + break + } + + if (!sorted) { + error( + "Failed to sort components: Couldn't find a row with ${component.unitWidth} empty slots to fit" + + "$component." + ) + } + + if (component is ComponentWithID) { + registry.register(component) + } + } + } + + public open fun MessageCreateBuilder.applyToMessage() { + sort() + + for (row in rows.filter { it.isNotEmpty() }) { + actionRow { + row.forEach { it.apply(this) } + } + } + } + + public open fun MessageModifyBuilder.applyToMessage() { + sort() + + for (row in rows.filter { it.isNotEmpty() }) { + actionRow { + row.forEach { it.apply(this) } + } + } + } +} + +public suspend fun ComponentContainer(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { + val container = ComponentContainer() + + builder(container) + + return container +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt new file mode 100644 index 0000000000..57cbb7adba --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt @@ -0,0 +1,134 @@ +package com.kotlindiscord.kord.extensions.components + +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder +import com.kotlindiscord.kord.extensions.checks.channelFor +import com.kotlindiscord.kord.extensions.checks.guildFor +import com.kotlindiscord.kord.extensions.checks.userFor +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.sentry.SentryContext +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.MemberBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.GuildChannelBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.Message +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.util.* + +public abstract class ComponentContext( + public open val component: Component, + public open val event: E +) : KoinComponent { + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + + /** Configured bot settings object. **/ + public val settings: ExtensibleBotBuilder by inject() + + /** Current Sentry context, containing breadcrumbs and other goodies. **/ + public val sentry: SentryContext = SentryContext() + + /** Cached locale variable, stored and retrieved by [getLocale]. **/ + public open var resolvedLocale: Locale? = null + + /** Channel this component was interacted with within. **/ + public open lateinit var channel: MessageChannelBehavior + + /** Guild this component was interacted with within, if any. **/ + public open var guild: GuildBehavior? = null + + /** Member that interacted with this component, if on a guild. **/ + public open var member: MemberBehavior? = null + + /** Member that interacted with this component, if on a guild. **/ + public open var message: Message? = null + + /** User that interacted with this component. **/ + public open lateinit var user: UserBehavior + + /** Called before processing, used to populate any extra variables from event data. **/ + public open suspend fun populate() { + channel = getChannel() + guild = getGuild() + member = getMember() + message = getMessage() + user = getUser() + } + + /** Extract channel information from event data, if that context is available. **/ + @JvmName("getChannel1") + public fun getChannel(): MessageChannelBehavior = + event.interaction.channel + + /** Extract guild information from event data, if that context is available. **/ + @JvmName("getGuild1") + public fun getGuild(): GuildBehavior? = + (event.interaction.channel as? GuildChannelBehavior)?.guild + + /** Extract member information from event data, if that context is available. **/ + @JvmName("getMember1") + public suspend fun getMember(): MemberBehavior? = + this.guild?.getMember(event.interaction.user.id) + + /** Extract message information from event data, if that context is available. **/ + @JvmName("getMessage1") + public fun getMessage(): Message? = + event.interaction.message + + /** Extract user information from event data, if that context is available. **/ + @JvmName("getUser1") + public fun getUser(): UserBehavior = + event.interaction.user + + /** Resolve the locale for this command context. **/ + public suspend fun getLocale(): Locale { + var locale: Locale? = resolvedLocale + + if (locale != null) { + return locale + } + + val guild = guildFor(event) + val channel = channelFor(event) + val user = userFor(event) + + for (resolver in settings.i18nBuilder.localeResolvers) { + val result = resolver(guild, channel, user) + + if (result != null) { + locale = result + break + } + } + + resolvedLocale = locale ?: settings.i18nBuilder.defaultLocale + + return resolvedLocale!! + } + + /** + * Given a translation key and bundle name, return the translation for the locale provided by the bot's configured + * locale resolvers. + */ + public suspend fun translate( + key: String, + bundleName: String?, + replacements: Array = arrayOf() + ): String { + val locale = getLocale() + + return translationsProvider.translate(key, locale, bundleName, replacements) + } + + /** + * Given a translation key and possible replacements,return the translation for the given locale in the + * extension's configured bundle, for the locale provided by the bot's configured locale resolvers. + */ + public suspend fun translate(key: String, replacements: Array = arrayOf()): String = translate( + key, + component.bundle, + replacements + ) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt new file mode 100644 index 0000000000..4ea24d4f76 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt @@ -0,0 +1,56 @@ +package com.kotlindiscord.kord.extensions.components + +import com.kotlindiscord.kord.extensions.components.buttons.InteractionButtonWithAction +import com.kotlindiscord.kord.extensions.components.menus.SelectMenu +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import mu.KLogger +import mu.KotlinLogging + +public open class ComponentRegistry { + internal val logger: KLogger = KotlinLogging.logger {} + + public open val components: MutableMap = mutableMapOf() + + public open fun register(component: ComponentWithID) { + logger.debug { "Registering component with ID: ${component.id}" } + + components[component.id] = component + } + + public open fun unregister(component: ComponentWithID): Component? = + unregister(component.id) + + public open fun unregister(id: String): Component? { + return components.remove(id) + } + + public suspend fun handle(event: ButtonInteractionCreateEvent) { + val id = event.interaction.componentId + + when (val c = components[id]) { + is InteractionButtonWithAction<*> -> { c.call(event) } + + null -> logger.warn { "Button interaction received for unknown component ID: $id" } + + else -> logger.warn { + "Button interaction received for component ($id), but that component is not a button component with " + + "an action" + } + } + } + + public suspend fun handle(event: SelectMenuInteractionCreateEvent) { + val id = event.interaction.componentId + + when (val c = components[id]) { + is SelectMenu<*> -> { c.call(event) } + + null -> logger.warn { "Select Menu interaction received for unknown component ID: $id" } + + else -> logger.warn { + "Select Menu interaction received for component ($id), but that component is not a select menu" + } + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt new file mode 100644 index 0000000000..8f68a954cd --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -0,0 +1,125 @@ +package com.kotlindiscord.kord.extensions.components + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.utils.getLocale +import com.kotlindiscord.kord.extensions.utils.permissionsForMember +import com.kotlindiscord.kord.extensions.utils.translate +import dev.kord.common.entity.Permission +import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent +import mu.KLogger +import mu.KotlinLogging + +public abstract class ComponentWithAction> + : ComponentWithID() { + private val logger: KLogger = KotlinLogging.logger {} + + public open var deferredAck: Boolean = true + + /** @suppress **/ + public open val checkList: MutableList> = mutableListOf() + + /** Permissions required to be able to run execute this component's action. **/ + public open val requiredPerms: MutableSet = mutableSetOf() + + /** Component body, to be called when the component is interacted with. **/ + public lateinit var body: suspend C.() -> Unit + + /** Call this to supply a component [body], to be called when the component is interacted with. **/ + public fun action(action: suspend C.() -> Unit) { + body = action + } + + /** + * Define a check which must pass for the component's body to be executed. + * + * A component may have multiple checks - all checks must pass for the component's body to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to this command. + */ + public open fun check(vararg checks: Check) { + checks.forEach { checkList.add(it) } + } + + /** + * Overloaded check function to allow for DSL syntax. + * + * @param check Check to apply to this command. + */ + public open fun check(check: Check) { + checkList.add(check) + } + + override fun validate() { + super.validate() + + if (!::body.isInitialized) { + error("No component body given.") + } + } + + /** If your bot requires permissions to be able to execute this component's body, add them using this function. **/ + public fun requirePermissions(vararg perms: Permission) { + perms.forEach { requiredPerms.add(it) } + } + + /** Runs standard checks that can be handled in a generic way, without worrying about subclass-specific checks. **/ + @Throws(CommandException::class) + public open suspend fun runStandardChecks(event: E): Boolean { + val locale = event.getLocale() + + checkList.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + return true + } + + /** Override this in order to implement any subclass-specific checks. **/ + @Throws(CommandException::class) + public open suspend fun runChecks(event: E): Boolean = + runStandardChecks(event) + + + + /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ + @Throws(CommandException::class) + public open suspend fun checkBotPerms(context: C) { + if (context.guild != null) { + val perms = (context.channel.asChannel() as GuildChannel) + .permissionsForMember(kord.selfId) + + val missingPerms = requiredPerms.filter { !perms.contains(it) } + + if (missingPerms.isNotEmpty()) { + throw CommandException( + context.translate( + "commands.error.missingBotPermissions", + null, + + replacements = arrayOf( + missingPerms.map { it.translate(context.getLocale()) }.joinToString(", ") + ) + ) + ) + } + } + } + + /** Override this to implement your component's calling logic. Check subtypes for examples! **/ + public abstract suspend fun call(event: E) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithID.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithID.kt new file mode 100644 index 0000000000..85d357e978 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithID.kt @@ -0,0 +1,13 @@ +package com.kotlindiscord.kord.extensions.components + +import java.util.* + +public abstract class ComponentWithID : Component() { + public open var id: String = UUID.randomUUID().toString() + + public override fun validate() { + if (id.isEmpty()) { + error("All components must have a unique ID.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt deleted file mode 100644 index 4366a8775e..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Components.kt +++ /dev/null @@ -1,296 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components - -import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext -import com.kotlindiscord.kord.extensions.components.builders.* -import com.kotlindiscord.kord.extensions.events.EventHandler -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.extensions.event -import dev.kord.common.annotation.KordPreview -import dev.kord.core.Kord -import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import dev.kord.rest.builder.message.create.MessageCreateBuilder -import dev.kord.rest.builder.message.create.actionRow -import dev.kord.rest.builder.message.modify.MessageModifyBuilder -import dev.kord.rest.builder.message.modify.actionRow -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import mu.KotlinLogging -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -private const val COMPONENTS_PER_ROW = 5 - -/** - * Class in charge of keeping track of sets of components, organising them into rows and handling their actions. - * - * Row specification is optional - by default, this class will try to sort components into rows automatically, filling - * in any available spots from top to bottom. If you don't want this, you can specify the row number and the component - * will be placed at the end of the row. - * - * When [parentContext] is provided, actionable component behaviour will match the slash command's current ack type. - * - * You most likely don't want to instantiate this class yourself - check the `components` DSL function available in - * all message creation contexts instead. - * - * @param extension Extension object parenting this `Components` instance. - * @param parentContext Optionally, parent slash command context. - */ -public open class Components( - public open val extension: Extension, - public open val parentContext: SlashCommandContext<*, *>? = null -) : KoinComponent { - private val logger = KotlinLogging.logger {} - - /** Current Kord instance. **/ - public val kord: Kord by inject() - - /** Current event handler instance waiting for interaction creation events. **/ - public var eventHandler: EventHandler? = null - - /** @suppress Internal Job object representing the timeout job. **/ - public var delayJob: Job? = null - - /** List of components that have yet to be sorted into rows. **/ - public open val unsortedComponents: MutableList = mutableListOf() - - /** Mapping of UUID to actionable component builder, used for handling interactions. **/ - public open val actionableComponents: MutableMap> = mutableMapOf() - - /** Predefined row structure, a 5x5 2D array of nulls. Filled in with component builders later. **/ - public open val rows: Array> = arrayOf( - // Up to 5 rows of components - - mutableListOf(), - mutableListOf(), - mutableListOf(), - mutableListOf(), - mutableListOf(), - ) - - /** List of registered timeout callbacks. **/ - public open val timeoutCallbacks: MutableList Unit> = mutableListOf() - - /** - * Create an interactive button that may be clicked on. - * - * @see InteractiveButtonBuilder - */ - public open suspend fun interactiveButton( - row: Int? = null, - builder: suspend InteractiveButtonBuilder.() -> Unit - ): InteractiveButtonBuilder { - val buttonBuilder = InteractiveButtonBuilder() - - if (parentContext == null) { - buttonBuilder.autoAck = AutoAckType.PUBLIC - } - - builder.invoke(buttonBuilder) - addComponent(buttonBuilder, row) - - actionableComponents[buttonBuilder.id] = buttonBuilder - - return buttonBuilder - } - - /** - * Create a link button that directs users to a URL when clicked. - * - * @see LinkButtonBuilder - */ - public open suspend fun linkButton( - row: Int? = null, - builder: suspend LinkButtonBuilder.() -> Unit - ): LinkButtonBuilder { - val buttonBuilder = LinkButtonBuilder() - - builder.invoke(buttonBuilder) - addComponent(buttonBuilder, row) - - return buttonBuilder - } - - /** - * Create a disabled interactive button, which does nothing when clicked. - * - * @see DisabledButtonBuilder - */ - public open suspend fun disabledButton( - row: Int? = null, - builder: suspend DisabledButtonBuilder.() -> Unit - ): DisabledButtonBuilder { - val buttonBuilder = DisabledButtonBuilder() - - builder.invoke(buttonBuilder) - addComponent(buttonBuilder, row) - - return buttonBuilder - } - - /** - * Create a dropdown menu, allowing users to select one or more values. - * - * @see MenuBuilder - */ - public open suspend fun menu( - row: Int? = null, - builder: suspend MenuBuilder.() -> Unit - ): MenuBuilder { - val menuBuilder = MenuBuilder() - - builder.invoke(menuBuilder) - addComponent(menuBuilder, row) - - actionableComponents[menuBuilder.id] = menuBuilder - - return menuBuilder - } - - /** Register a callback to run when a setup timeout expires. **/ - public open fun onTimeout(body: suspend () -> Unit): Boolean = - timeoutCallbacks.add(body) - - /** @suppress Internal API function that runs the timeout callbacks. **/ - @Suppress("TooGenericExceptionCaught") - public open suspend fun runTimeoutCallbacks() { - timeoutCallbacks.forEach { - try { - it() - } catch (t: Throwable) { - logger.error(t) { "Error during timeout callback: $it" } - } - } - } - - /** - * @suppress Internal API function for validating the given row number and storing the component builder. - */ - public open fun addComponent(builder: ComponentBuilder, row: Int? = null) { - builder.validate() - - if (row == null) { - unsortedComponents.add(builder) - } else { - if (row < 0 || row >= rows.size) { - error("The given row number ($row) is invalid - it must be between 0 - ${rows.size - 1}, inclusive.") - } - - val components = rows[row] - - if (components.size >= COMPONENTS_PER_ROW) { - error( - "Row $row is full - up to $COMPONENTS_PER_ROW components are allowed per row, or 1 " + - "row-exclusive component." - ) - } - - if (components.isNotEmpty() && components.any { it.rowExclusive }) { - error("Row $row contains a row-exclusive component, and can't contain any other components.") - } - - components.add(builder) - } - } - - /** - * @suppress Internal API function that tries to sort components into rows as tightly as possible. - */ - public open fun sortIntoRows() { - while (unsortedComponents.isNotEmpty()) { - val component = unsortedComponents.removeFirst() - - val row = if (component.rowExclusive) { - rows.firstOrNull { it.isEmpty() } ?: error( - "Failed to sort components into rows - Component $component is row-exclusive, but there are no " + - "empty rows left." - ) - } else { - rows.firstOrNull { it.size < COMPONENTS_PER_ROW && !it.any { e -> e.rowExclusive } } ?: error( - "Failed to sort components into rows - all possible rows are full." - ) - } - - row.add(component) - } - } - - /** - * @suppress Internal API function that creates an event handler to listen for component interactions, with a - * timeout. - */ - @Suppress("MagicNumber") // Turning seconds into millis, again - public open suspend fun startListening(timeoutSeconds: Long? = null) { - val timeoutMillis = timeoutSeconds?.let { it * 1000 } - - eventHandler = extension.event { - booleanCheck { - val interaction = it.interaction as? ComponentInteraction - - interaction != null && interaction.componentId in actionableComponents - } - - action { - val interaction = event.interaction - val component = actionableComponents[interaction.componentId]!! - - component.call(this@Components, extension, event, parentContext) - } - } - - if (timeoutMillis != null) { - delayJob = kord.launch { - delay(timeoutMillis) - - eventHandler?.job?.cancel() - eventHandler = null - - runTimeoutCallbacks() - stop() - } - } - } - - /** - * Stop listening for interaction events. Actionable components will no longer function. - */ - public open fun stop() { - eventHandler?.job?.cancel() - delayJob?.cancel() - } - - /** - * @suppress Internal API function that sets up all of the components, adds them to the message, and listens for - * interactions. - */ - public open suspend fun MessageCreateBuilder.setup(timeoutSeconds: Long? = null) { - sortIntoRows() - - for (row in rows.filter { row -> row.isNotEmpty() }) { - actionRow { - row.forEach { it.apply(this) } - } - } - - startListening(timeoutSeconds) - } - - /** - * @suppress Internal API function that sets up all of the components, adds them to the message, and listens for - * interactions. - */ - public open suspend fun MessageModifyBuilder.setup(timeoutSeconds: Long? = null) { - sortIntoRows() - - for (row in rows.filter { row -> row.isNotEmpty() }) { - actionRow { - row.forEach { it.apply(this) } - } - } - - startListening(timeoutSeconds) - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt new file mode 100644 index 0000000000..7045e4293d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt @@ -0,0 +1,110 @@ +package com.kotlindiscord.kord.extensions.components + +import com.kotlindiscord.kord.extensions.components.buttons.DisabledInteractionButton +import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteractionButton +import com.kotlindiscord.kord.extensions.components.buttons.LinkInteractionButton +import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButton +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenu +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenu +import dev.kord.rest.builder.message.create.MessageCreateBuilder +import dev.kord.rest.builder.message.modify.MessageModifyBuilder + +public suspend fun ComponentContainer.disabledButton( + row: Int? = null, + builder: suspend DisabledInteractionButton.() -> Unit +): DisabledInteractionButton { + val component = DisabledInteractionButton() + + builder(component) + add(component, row) + + return component +} + +public suspend fun ComponentContainer.ephemeralButton( + row: Int? = null, + builder: suspend EphemeralInteractionButton.() -> Unit +): EphemeralInteractionButton { + val component = EphemeralInteractionButton() + + builder(component) + add(component, row) + + return component +} + +public suspend fun ComponentContainer.linkButton( + row: Int? = null, + builder: suspend LinkInteractionButton.() -> Unit +): LinkInteractionButton { + val component = LinkInteractionButton() + + builder(component) + add(component, row) + + return component +} + +public suspend fun ComponentContainer.publicButton( + row: Int? = null, + builder: suspend PublicInteractionButton.() -> Unit +): PublicInteractionButton { + val component = PublicInteractionButton() + + builder(component) + add(component, row) + + return component +} + +public suspend fun ComponentContainer.ephemeralSelectMenu( + row: Int? = null, + builder: suspend EphemeralSelectMenu.() -> Unit +): EphemeralSelectMenu { + val component = EphemeralSelectMenu() + + builder(component) + add(component, row) + + return component +} + +public suspend fun ComponentContainer.publicSelectMenu( + row: Int? = null, + builder: suspend PublicSelectMenu.() -> Unit +): PublicSelectMenu { + val component = PublicSelectMenu() + + builder(component) + add(component, row) + + return component +} + +public fun MessageCreateBuilder.applyComponents(components: ComponentContainer) { + with(components) { + applyToMessage() + } +} + +public fun MessageModifyBuilder.applyComponents(components: ComponentContainer) { + with(components) { + applyToMessage() + } +} + +public suspend fun MessageCreateBuilder.components(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { + val container = ComponentContainer(builder) + + applyComponents(container) + + return container +} + +public suspend fun MessageModifyBuilder.components(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { + val container = ComponentContainer(builder) + + applyComponents(container) + + return container +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt deleted file mode 100644 index 367ec8ffb3..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ActionableComponentBuilder.kt +++ /dev/null @@ -1,279 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import com.kotlindiscord.kord.extensions.CommandException -import com.kotlindiscord.kord.extensions.checks.* -import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.checks.types.CheckContext -import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext -import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext -import com.kotlindiscord.kord.extensions.components.AutoAckType -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.components.contexts.ActionableComponentContext -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import com.kotlindiscord.kord.extensions.sentry.tag -import com.kotlindiscord.kord.extensions.sentry.user -import com.kotlindiscord.kord.extensions.utils.ackEphemeral -import com.kotlindiscord.kord.extensions.utils.ackPublic -import com.kotlindiscord.kord.extensions.utils.getLocale -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.ButtonStyle -import dev.kord.core.behavior.interaction.InteractionResponseBehavior -import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.entity.channel.DmChannel -import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import mu.KotlinLogging -import org.koin.core.component.inject -import java.util.* - -/** - * Button builder representing an interactive button, with click action. - * - * Either a [label] or [emoji] must be provided. [style] must not be [ButtonStyle.Link]. - */ -public abstract class ActionableComponentBuilder> : - ComponentBuilder() { - private val logger = KotlinLogging.logger {} - private val translations: TranslationsProvider by inject() - - /** Unique ID for this button. Required by Discord. **/ - public open var id: String = UUID.randomUUID().toString() - - /** - * Automatic ack type, if not following a parent context. - */ - public open var autoAck: AutoAckType? = AutoAckType.EPHEMERAL - - /** - * Automatic ack type, if not following a parent context. - */ - @Deprecated( - "This property was renamed to autoAck for consistency.", - ReplaceWith("autoAck"), - DeprecationLevel.ERROR - ) - public open var ackType: AutoAckType? - get() = autoAck - set(value) { - autoAck = value - } - - /** Whether to send a deferred acknowledgement instead of a normal one. **/ - public open var deferredAck: Boolean = false - - /** Whether to follow the ack type of the parent slash command context, if any. **/ - public open var followParent: Boolean = true - - /** @suppress Internal variable, a list of checks to apply to click actions. **/ - public open val checks: MutableList> = mutableListOf() - - /** @suppress Internal variable, the click action to run. **/ - public open lateinit var body: suspend R.() -> Unit - - public override fun validate() { - if (!this::body.isInitialized) { - error("Actionable components must have an action defined.") - } - } - - /** Register a check that must pass for this button to be actioned. **/ - public open fun check(vararg checks: Check): Boolean = - this.checks.addAll(checks) - - /** Register a check that must pass for this button to be actioned. **/ - public open fun check(check: Check): Boolean = - checks.add(check) - - /** - * Define a simple Boolean check which must pass for the button to be actioned. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - */ - public open fun booleanCheck(vararg checks: suspend (ComponentInteractionCreateEvent) -> Boolean) { - checks.forEach(::booleanCheck) - } - - /** - * Overloaded simple Boolean check function to allow for DSL syntax. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - */ - public open fun booleanCheck(check: suspend (ComponentInteractionCreateEvent) -> Boolean) { - check { - if (check(event)) { - pass() - } else { - fail() - } - } - } - - /** Register the click action that should be run when this button is clicked, assuming the checks pass. **/ - public open fun action(action: suspend R.() -> Unit) { - this.body = action - } - - /** Run this component's checks, returning a Boolean representing whether the checks passed. **/ - public open suspend fun runChecks(event: ComponentInteractionCreateEvent, sendMessage: Boolean = true): Boolean { - val interaction = event.interaction as T - val locale = event.getLocale() - - for (check in checks) { - val context = CheckContext(event, locale) - - check(context) - - if (!context.passed) { - val message = context.message - - if (message != null && sendMessage) { - interaction.respondEphemeral { - content = translations.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - } - } else { - interaction.acknowledgeEphemeralDeferredMessageUpdate() - } - - return false - } - } - - return true - } - - override suspend fun call( - components: Components, - extension: Extension, - event: ComponentInteractionCreateEvent, - parentContext: SlashCommandContext<*, *>? - ) { - if (!runChecks(event)) { - return - } - - val sentryContext = SentryContext() - - if (sentry.enabled) { - sentryContext.breadcrumb(BreadcrumbType.User) { - category = "interaction" - type = "user" - message = "Interaction for component \"$id\" received." - - val channel = channelFor(event) - val guild = guildFor(event)?.asGuildOrNull() - - if (channel != null) { - data["channel"] = when (channel) { - is DmChannel -> "Private Message (${channel.id.asString})" - is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" - - else -> channel.id.asString - } - } - - if (guild != null) { - data["guild"] = "${guild.name} (${guild.id.asString})" - } - } - } - - val interaction = event.interaction as T - - val response = if (parentContext != null && followParent) { - when (parentContext is EphemeralSlashCommandContext<*>) { - true -> interaction.ackEphemeral(deferredAck) - false -> interaction.ackPublic(deferredAck) - - else -> null - } - } else { - when (autoAck) { - AutoAckType.EPHEMERAL -> interaction.ackEphemeral(deferredAck) - AutoAckType.PUBLIC -> interaction.ackPublic(deferredAck) - - else -> null - } - } - - val context = getContext( - extension, event, components, response, interaction, sentryContext - ) - - context.populate() - - @Suppress("TooGenericExceptionCaught") - try { - body(context) - } catch (e: CommandException) { - context.respond(e.toString()) - } catch (t: Throwable) { - if (sentry.enabled) { - val user = userFor(event)?.asUserOrNull() - val channel = channelFor(event)?.asChannelOrNull() - - val sentryId = sentryContext.captureException(t, "Component interaction execution failed.") { - logger.debug { "Submitting error to sentry." } - - if (user != null) { - user(user) - } - - tag("private", "false") - - if (channel is DmChannel) { - tag("private", "true") - } - - tag("extension", extension.name) - } - - logger.debug { "Error submitted to Sentry: $sentryId" } - - sentry.addEventId(sentryId) - - logger.error(t) { "Error thrown during component interaction" } - - if (extension.bot.extensions.containsKey("sentry")) { - context.respond( - context.translate( - "commands.error.user.sentry.slash", - null, - replacements = arrayOf(sentryId) - ) - ) - } else { - context.respond( - context.translate("commands.error.user") - ) - } - } else { - logger.error(t) { "Error thrown during component interaction" } - - context.respond(context.translate("commands.error.user", null)) - } - } - } - - /** Function to be overridden in order to retrieve a context object of the correct type. **/ - public abstract fun getContext( - extension: Extension, - event: ComponentInteractionCreateEvent, - components: Components, - interactionResponse: InteractionResponseBehavior? = null, - interaction: T = event.interaction as T, - sentryContext: SentryContext - ): R -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ButtonBuilder.kt deleted file mode 100644 index 92b2eb6234..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ButtonBuilder.kt +++ /dev/null @@ -1,69 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.DiscordPartialEmoji -import dev.kord.common.entity.optional.optional -import dev.kord.core.entity.GuildEmoji -import dev.kord.core.entity.ReactionEmoji -import org.koin.core.component.KoinComponent - -/** - * Abstract class representing a button builder, providing common functionality and properties. - */ -public interface ButtonBuilder : KoinComponent { - /** - * The button's label text. Optional if you've got an emoji. - * - * Labels default to a zero-width space. This does make them slightly wider than usual if you don't set your - * own label, but it means that iOS users can tap emoji-only buttons without having to specifically tap the - * emoji. - */ - public var label: String? - - /** - * A partial emoji object, either a guild or Unicode emoji. Optional if you've got a label. - * - * @see emoji - */ - public var partialEmoji: DiscordPartialEmoji? - - /** Convenience function for setting [partialEmoji] based on a given Unicode emoji. **/ - public fun emoji(unicodeEmoji: String) { - partialEmoji = DiscordPartialEmoji( - name = unicodeEmoji - ) - } - - /** Convenience function for setting [partialEmoji] based on a given guild custom emoji. **/ - public fun emoji(guildEmoji: GuildEmoji) { - partialEmoji = DiscordPartialEmoji( - id = guildEmoji.id, - name = guildEmoji.name, - animated = guildEmoji.isAnimated.optional() - ) - } - - /** Convenience function for setting [partialEmoji] based on a given reaction emoji. **/ - public fun emoji(unicodeEmoji: ReactionEmoji.Unicode) { - partialEmoji = DiscordPartialEmoji( - name = unicodeEmoji.name - ) - } - - /** Convenience function for setting [partialEmoji] based on a given reaction emoji. **/ - public fun emoji(guildEmoji: ReactionEmoji.Custom) { - partialEmoji = DiscordPartialEmoji( - id = guildEmoji.id, - name = guildEmoji.name, - animated = guildEmoji.isAnimated.optional() - ) - } - - /** Convenience function for setting [partialEmoji] based on a given reaction emoji. **/ - public fun emoji(emoji: ReactionEmoji): Unit = when (emoji) { - is ReactionEmoji.Unicode -> emoji(emoji) - is ReactionEmoji.Custom -> emoji(emoji) - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt deleted file mode 100644 index 666ed4c6a2..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/ComponentBuilder.kt +++ /dev/null @@ -1,51 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.sentry.SentryAdapter -import dev.kord.common.annotation.KordPreview -import dev.kord.core.Kord -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import dev.kord.rest.builder.component.ActionRowBuilder -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/** - * Abstract class representing a button builder, providing common functionality and properties. - */ -public abstract class ComponentBuilder : KoinComponent { - /** The [ExtensibleBot] instance that this extension is installed to. **/ - public val bot: ExtensibleBot by inject() - - /** Current Kord instance powering the bot. **/ - public val kord: Kord by inject() - - /** Sentry adapter, for easy access to Sentry functions. **/ - public val sentry: SentryAdapter by inject() - - /** Whether this component must be in a row on its own. **/ - public open val rowExclusive: Boolean = false - - /** Function used to add this button to an action row. **/ - public abstract fun apply(builder: ActionRowBuilder) - - /** Function called to validate the button. Should throw exceptions if something is invalid. **/ - public abstract fun validate() - - /** - * For interactive button types, called in order to action the button. Throws [UnsupportedOperationException] - * by default. - */ - public open suspend fun call( - components: Components, - extension: Extension, - event: ComponentInteractionCreateEvent, - parentContext: SlashCommandContext<*, *>? = null - ) { - throw UnsupportedOperationException("This type of component doesn't support callable actions.") - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/DisabledButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/DisabledButtonBuilder.kt deleted file mode 100644 index 92640ba685..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/DisabledButtonBuilder.kt +++ /dev/null @@ -1,46 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.ButtonStyle -import dev.kord.common.entity.DiscordPartialEmoji -import dev.kord.rest.builder.component.ActionRowBuilder -import java.util.* - -/** - * Button builder representing a disabled interactive button. No click action. - * - * Either a [label] or [emoji] must be provided. [style] must not be [ButtonStyle.Link]. - */ -public open class DisabledButtonBuilder : ButtonBuilder, ComponentBuilder() { - /** Unique ID for this button. Required by Discord. **/ - public open var id: String = UUID.randomUUID().toString() - - /** Button style. **/ - public open var style: ButtonStyle = ButtonStyle.Primary - - override var label: String? = null - override var partialEmoji: DiscordPartialEmoji? = null - - public override fun apply(builder: ActionRowBuilder) { - builder.interactionButton(style, id) { - emoji = partialEmoji - - // ZWSP, so iOS users don't have to directly tap an emoji if there's no label - label = this@DisabledButtonBuilder.label ?: "\u200B" - - disabled = true - } - } - - public override fun validate() { - if (label == null && partialEmoji == null) { - error("Disabled buttons must have either a label or emoji.") - } - - if (style == ButtonStyle.Link) { - error("The Link button style is reserved for link buttons.") - } - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt deleted file mode 100644 index 813f4f65bf..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/InteractiveButtonBuilder.kt +++ /dev/null @@ -1,64 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.components.contexts.InteractiveButtonContext -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.ButtonStyle -import dev.kord.common.entity.DiscordPartialEmoji -import dev.kord.core.behavior.interaction.InteractionResponseBehavior -import dev.kord.core.entity.interaction.ButtonInteraction -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import dev.kord.rest.builder.component.ActionRowBuilder -import mu.KotlinLogging - -private val logger = KotlinLogging.logger {} - -/** - * Button builder representing an interactive button, with click action. - * - * Either a [label] or [emoji] must be provided. [style] must not be [ButtonStyle.Link]. - */ -public open class InteractiveButtonBuilder : ButtonBuilder, - ActionableComponentBuilder() { - /** Button style. **/ - public open var style: ButtonStyle = ButtonStyle.Primary - - override var label: String? = null - override var partialEmoji: DiscordPartialEmoji? = null - - public override fun apply(builder: ActionRowBuilder) { - builder.interactionButton(style, id) { - emoji = partialEmoji - - // ZWSP, so iOS users don't have to directly tap an emoji if there's no label - label = this@InteractiveButtonBuilder.label ?: "\u200B" - } - } - - public override fun validate() { - if (label == null && partialEmoji == null) { - error("Interactive buttons must have either a label or emoji.") - } - - if (style == ButtonStyle.Link) { - error("The Link button style is reserved for link buttons.") - } - - super.validate() - } - - override fun getContext( - extension: Extension, - event: ComponentInteractionCreateEvent, - components: Components, - interactionResponse: InteractionResponseBehavior?, - interaction: ButtonInteraction, - sentryContext: SentryContext - ): InteractiveButtonContext = InteractiveButtonContext( - extension, event, components, interactionResponse, interaction, sentryContext - ) -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/LinkButtonBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/LinkButtonBuilder.kt deleted file mode 100644 index dfae319a4c..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/LinkButtonBuilder.kt +++ /dev/null @@ -1,39 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import dev.kord.common.annotation.KordPreview -import dev.kord.common.entity.DiscordPartialEmoji -import dev.kord.rest.builder.component.ActionRowBuilder - -/** - * Button builder representing a link button that directs users to a URL. - * - * Either a [label] or [emoji] must be provided. A [url] is also required. - */ -public open class LinkButtonBuilder : ButtonBuilder, ComponentBuilder() { - /** URL to direct users to when clicked. **/ - public open lateinit var url: String - - override var label: String? = null - override var partialEmoji: DiscordPartialEmoji? = null - - public override fun apply(builder: ActionRowBuilder) { - builder.linkButton(url) { - emoji = partialEmoji - - // ZWSP, so iOS users don't have to directly tap an emoji if there's no label - label = this@LinkButtonBuilder.label ?: "\u200B" - } - } - - public override fun validate() { - if (label == null && partialEmoji == null) { - error("Link buttons must have either a label or emoji.") - } - - if (!this::url.isInitialized) { - error("Link buttons must have a URL.") - } - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt deleted file mode 100644 index 0768109390..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/builders/MenuBuilder.kt +++ /dev/null @@ -1,103 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.components.builders - -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.components.contexts.MenuContext -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.interaction.InteractionResponseBehavior -import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.component.SelectOptionBuilder - -private const val DESCRIPTION_MAX = 50 -private const val LABEL_MAX = 25 -private const val OPTIONS_MAX = 25 -private const val PLACEHOLDER_MAX = 100 -private const val VALUE_MAX = 100 - -/** - * Builder representing a dropdown menu on Discord. - * - * At least one option must be provided. - */ -public open class MenuBuilder : ActionableComponentBuilder() { - /** List of options for the user to choose from. **/ - public val options: MutableList = mutableListOf() - - /** The minimum number of choices that the user must make. **/ - public var minimumChoices: Int = 1 - - /** The maximum number of choices that the user can make. Set to `null` for no maximum. **/ - public var maximumChoices: Int? = 1 - - /** Placeholder text to show before the user has selected any options.. **/ - public var placeholder: String? = null - - // Menus can only be on their own in a row. - override val rowExclusive: Boolean = true - - /** Add an option to this select menu. **/ - public suspend fun option(label: String, value: String, body: suspend SelectOptionBuilder.() -> Unit = {}) { - val builder = SelectOptionBuilder(label, value) - - body(builder) - - if (builder.description?.length ?: 0 > DESCRIPTION_MAX) { - error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.") - } - - if (builder.label.length > LABEL_MAX) { - error("Option labels must not be longer than $LABEL_MAX characters.") - } - - if (builder.value.length > VALUE_MAX) { - error("Option values must not be longer than $VALUE_MAX characters.") - } - - options.add(builder) - } - - public override fun apply(builder: ActionRowBuilder) { - if (maximumChoices == null || maximumChoices!! > options.size) { - maximumChoices = options.size - } - - builder.selectMenu(id) { - allowedValues = minimumChoices..maximumChoices!! - - this.options.addAll(this@MenuBuilder.options) - this.placeholder = this@MenuBuilder.placeholder - } - } - - override fun validate() { - if (this.options.isEmpty()) { - error("Menu components must have at least one option.") - } - - if (this.options.size > OPTIONS_MAX) { - error("Menu components must not have more than $OPTIONS_MAX options.") - } - - if (this.placeholder?.length ?: 0 > PLACEHOLDER_MAX) { - error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") - } - - super.validate() - } - - override fun getContext( - extension: Extension, - event: ComponentInteractionCreateEvent, - components: Components, - interactionResponse: InteractionResponseBehavior?, - interaction: SelectMenuInteraction, - sentryContext: SentryContext - ): MenuContext = MenuContext( - extension, event, components, interactionResponse, interaction, sentryContext - ) -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt new file mode 100644 index 0000000000..a06266d019 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt @@ -0,0 +1,25 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import dev.kord.common.entity.ButtonStyle +import dev.kord.rest.builder.component.ActionRowBuilder + +public open class DisabledInteractionButton : InteractionButtonWithID() { + public open var style: ButtonStyle = ButtonStyle.Primary + + override fun apply(builder: ActionRowBuilder) { + builder.interactionButton(style, id) { + emoji = partialEmoji + label = this@DisabledInteractionButton.label + + disabled = true + } + } + + override fun validate() { + super.validate() + + if (style == ButtonStyle.Link) { + error("The Link button style is reserved for link buttons.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt new file mode 100644 index 0000000000..f5323a56cb --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -0,0 +1,73 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.common.entity.ButtonStyle +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder + +public typealias InitialEphemeralButtonResponseBuilder = + (suspend EphemeralInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? + +public open class EphemeralInteractionButton : InteractionButtonWithAction() { + public open var style: ButtonStyle = ButtonStyle.Primary + public open var initialResponseBuilder: InitialEphemeralButtonResponseBuilder = null + + override fun apply(builder: ActionRowBuilder) { + builder.interactionButton(style, id) { + emoji = partialEmoji + label = this@EphemeralInteractionButton.label + } + } + + override suspend fun call(event: ButtonInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondEphemeral { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else { + if (!deferredAck) { + event.interaction.acknowledgeEphemeralDeferredMessageUpdate() + } else { + event.interaction.acknowledgeEphemeral() + } + } + + val context = EphemeralInteractionButtonContext(this, event, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override fun validate() { + super.validate() + + if (style == ButtonStyle.Link) { + error("The Link button style is reserved for link buttons.") + } + } + + override suspend fun respondText(context: EphemeralInteractionButtonContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt new file mode 100644 index 0000000000..8f537398f8 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt @@ -0,0 +1,11 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent + +public class EphemeralInteractionButtonContext( + override val component: EphemeralInteractionButton, + override val event: ButtonInteractionCreateEvent, + override val interactionResponse: EphemeralInteractionResponseBehavior +) : InteractionButtonContext(component, event), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt new file mode 100644 index 0000000000..8351fd2c37 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt @@ -0,0 +1,16 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji +import dev.kord.common.entity.DiscordPartialEmoji + +public abstract class InteractionButton : Component(), HasPartialEmoji { + public var label: String? = null + public override var partialEmoji: DiscordPartialEmoji? = null + + override fun validate() { + if (label == null && partialEmoji == null) { + error("Buttons must have either a label or emoji.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt new file mode 100644 index 0000000000..f272b87af2 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt @@ -0,0 +1,10 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.ComponentContext +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent + +public abstract class InteractionButtonContext( + component: Component, + event: ButtonInteractionCreateEvent +) : ComponentContext(component, event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt new file mode 100644 index 0000000000..cfcec9a5b6 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt @@ -0,0 +1,106 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.components.ComponentWithAction +import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.tag +import com.kotlindiscord.kord.extensions.sentry.user +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.core.entity.channel.DmChannel +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging + +public abstract class InteractionButtonWithAction + : ComponentWithAction(), HasPartialEmoji { + internal val logger: KLogger = KotlinLogging.logger {} + + public var label: String? = null + public override var partialEmoji: DiscordPartialEmoji? = null + + /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ + public open suspend fun firstSentryBreadcrumb(context: C, button: InteractionButtonWithAction<*>) { + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "component.button" + message = "Button \"${button.id}\" clicked." + + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() + val message = context.message + + data["component"] = button.id + + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } + } + + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } + + if (message != null) { + data["message"] = message.id.asString + } + } + } + } + + /** A general way to handle errors thrown during the course of a button action's execution. **/ + public open suspend fun handleError(context: C, t: Throwable, button: InteractionButtonWithAction<*>) { + logger.error(t) { "Error during execution of button (${button.id}) action (${context.event})" } + + if (sentry.enabled) { + logger.debug { "Submitting error to sentry." } + + val channel = context.channel + val author = context.user.asUserOrNull() + + val sentryId = context.sentry.captureException(t, "Button action execution failed.") { + if (author != null) { + user(author) + } + + tag("private", "false") + + if (channel is DmChannel) { + tag("private", "true") + } + + tag("component", button.id) + + Sentry.captureException(t, "Slash command execution failed.") + } + + logger.debug { "Error submitted to Sentry: $sentryId" } + + val errorMessage = if (bot.extensions.containsKey("sentry")) { + context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + } else { + context.translate("commands.error.user", null) + } + + respondText(context, errorMessage) + } else { + respondText(context, context.translate("commands.error.user", null)) + } + } + + override fun validate() { + super.validate() + + if (label == null && partialEmoji == null) { + error("Buttons must have either a label or emoji.") + } + } + + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ + public abstract suspend fun respondText(context: C, message: String) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt new file mode 100644 index 0000000000..0bae06dc8d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt @@ -0,0 +1,18 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.components.ComponentWithID +import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji +import dev.kord.common.entity.DiscordPartialEmoji + +public abstract class InteractionButtonWithID : ComponentWithID(), HasPartialEmoji { + public var label: String? = null + public override var partialEmoji: DiscordPartialEmoji? = null + + override fun validate() { + super.validate() + + if (label == null && partialEmoji == null) { + error("Buttons must have either a label or emoji.") + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt new file mode 100644 index 0000000000..6f228d1580 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt @@ -0,0 +1,22 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import dev.kord.rest.builder.component.ActionRowBuilder + +public open class LinkInteractionButton : InteractionButton() { + public open lateinit var url: String + + override fun validate() { + super.validate() + + if (!this::url.isInitialized) { + error("Link buttons must have a URL.") + } + } + + override fun apply(builder: ActionRowBuilder) { + builder.linkButton(url) { + emoji = partialEmoji + label = this@LinkInteractionButton.label + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt new file mode 100644 index 0000000000..e7fe060942 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -0,0 +1,73 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.common.entity.ButtonStyle +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder + +public typealias InitialPublicButtonResponseBuilder = + (suspend PublicInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? + +public open class PublicInteractionButton : InteractionButtonWithAction() { + public open var style: ButtonStyle = ButtonStyle.Primary + public open var initialResponseBuilder: InitialPublicButtonResponseBuilder = null + + override fun apply(builder: ActionRowBuilder) { + builder.interactionButton(style, id) { + emoji = partialEmoji + label = this@PublicInteractionButton.label + } + } + + override suspend fun call(event: ButtonInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else { + if (!deferredAck) { + event.interaction.acknowledgePublic() + } else { + event.interaction.acknowledgePublicDeferredMessageUpdate() + } + } + + val context = PublicInteractionButtonContext(this, event, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override fun validate() { + super.validate() + + if (style == ButtonStyle.Link) { + error("The Link button style is reserved for link buttons.") + } + } + + override suspend fun respondText(context: PublicInteractionButtonContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt new file mode 100644 index 0000000000..ff37f53466 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt @@ -0,0 +1,11 @@ +package com.kotlindiscord.kord.extensions.components.buttons + +import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent + +public class PublicInteractionButtonContext( + component: PublicInteractionButton, + event: ButtonInteractionCreateEvent, + override val interactionResponse: PublicInteractionResponseBehavior +) : InteractionButtonContext(component, event), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/ActionableComponentContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/ActionableComponentContext.kt deleted file mode 100644 index d97327a7dc..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/ActionableComponentContext.kt +++ /dev/null @@ -1,216 +0,0 @@ -package com.kotlindiscord.kord.extensions.components.contexts - -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.checks.channelFor -import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.memberFor -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.MemberBehavior -import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.Guild -import dev.kord.core.entity.channel.MessageChannel -import dev.kord.core.entity.interaction.ComponentInteraction -import dev.kord.core.entity.interaction.InteractionFollowup -import dev.kord.core.entity.interaction.PublicFollowupMessage -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import java.util.* - -/** - * Context object representing the execution context of an actionable component interaction. - * - * @property extension Extension object this interaction happened within. - * @property event Event that was fired for this interaction. - * @property components Components container this button belongs to. - * @property interactionResponse Interaction response, if automatically acked. - * @property interaction Convenience access to the properly-typed interaction object. - * @property sentry Current Sentry context, containing breadcrumbs and other goodies. - */ -@OptIn(KordPreview::class) -@ExtensionDSL -public abstract class ActionableComponentContext( - public open val extension: Extension, - public open val event: ComponentInteractionCreateEvent, - public open val components: Components, - public open var interactionResponse: InteractionResponseBehavior? = null, - public open val interaction: T = event.interaction as T, - public open val sentry: SentryContext -) : KoinComponent { - /** Translations provider, for retrieving translations. **/ - public val translationsProvider: TranslationsProvider by inject() - - /** Cached locale variable, stored and retrieved by [getLocale]. **/ - public open var resolvedLocale: Locale? = null - - /** Whether a response or ack has already been sent by the user. **/ - public open val acked: Boolean get() = interactionResponse != null - - /** Channel this interaction happened in. **/ - public open lateinit var channel: MessageChannel - - /** Guild this interaction happened in. **/ - public open var guild: Guild? = null - - /** Guild member responsible for executing this interaction. **/ - public open var member: MemberBehavior? = null - - /** User responsible for executing this interaction. **/ - public open lateinit var user: UserBehavior - - /** Whether we're working ephemerally, or null if no ack or response was sent yet. **/ - public open val isEphemeral: Boolean? - get() = when (interactionResponse) { - is EphemeralInteractionResponseBehavior -> true - is PublicInteractionResponseBehavior -> false - - else -> null - } - - /** Called before processing, used to populate any extra variables from event data. **/ - public suspend fun populate() { - channel = getChannel() - guild = getGuild() - member = getMember() - user = getUser() - } - - /** Extract channel information from event data, if that context is available. **/ - public suspend fun getChannel(): MessageChannel = channelFor(event)!!.asChannel() as MessageChannel - - /** Extract guild information from event data, if that context is available. **/ - public suspend fun getGuild(): Guild? = guildFor(event)?.asGuildOrNull() - - /** Extract member information from event data, if that context is available. **/ - public suspend fun getMember(): MemberBehavior? = memberFor(event)?.asMemberOrNull() - - /** Extract user information from event data, if that context is available. **/ - public suspend fun getUser(): UserBehavior = event.interaction.user - - /** - * Send an acknowledgement manually, assuming you have `autoAck` set to `NONE`. - * - * Note that what you supply for `ephemeral` will decide how the rest of your interactions - both responses and - * follow-ups. They must match in ephemeral state. - * - * This function will throw an exception if an acknowledgement or response has already been sent. - * - * @param ephemeral Whether this should be an ephemeral acknowledgement or not. - */ - public suspend fun ack(ephemeral: Boolean): InteractionResponseBehavior { - if (acked) { - error("Attempted to acknowledge an interaction that's already been acknowledged.") - } - - interactionResponse = if (ephemeral) { - event.interaction.acknowledgeEphemeral() - } else { - event.interaction.acknowledgePublic() - } - - return interactionResponse!! - } - - /** - * Assuming an acknowledgement or response has been sent, send an ephemeral follow-up message. - * - * This function will throw an exception if no acknowledgement or response has been sent yet, or this interaction - * has already been interacted with in a non-ephemeral manner. - * - * Note that ephemeral follow-ups require a content string, and may not contain embeds or files. - */ - public suspend inline fun ephemeralFollowUp( - builder: EphemeralFollowupMessageCreateBuilder.() -> Unit = {} - ): InteractionFollowup { - if (interactionResponse == null) { - error("Tried send an interaction follow-up before acknowledging it.") - } - - if (isEphemeral == false) { - error("Tried send an ephemeral follow-up for a public interaction.") - } - - return (interactionResponse as EphemeralInteractionResponseBehavior).followUpEphemeral(builder) - } - - /** - * Assuming an acknowledgement or response has been sent, send a public follow-up message. - * - * This function will throw an exception if no acknowledgement or response has been sent yet, or this interaction - * has already been interacted with in an ephemeral manner. - */ - public suspend inline fun publicFollowUp( - builder: PublicFollowupMessageCreateBuilder.() -> Unit - ): PublicFollowupMessage { - if (interactionResponse == null) { - error("Tried send an interaction follow-up before acknowledging it.") - } - - if (isEphemeral == true) { - error("Tried to send a public follow-up for an ephemeral interaction.") - } - - return (interactionResponse as PublicInteractionResponseBehavior).followUp(builder) - } - - /** Resolve the locale for this context. **/ - public suspend fun getLocale(): Locale { - var locale: Locale? = resolvedLocale - - if (locale != null) { - return locale - } - - for (resolver in extension.bot.settings.i18nBuilder.localeResolvers) { - val result = resolver(guild, channel, user) - - if (result != null) { - locale = result - break - } - } - - resolvedLocale = locale ?: extension.bot.settings.i18nBuilder.defaultLocale - - return resolvedLocale!! - } - - /** - * Given a translation key and bundle name, return the translation for the locale provided by the bot's configured - * locale resolvers. - */ - public suspend fun translate( - key: String, - bundleName: String?, - replacements: Array = arrayOf() - ): String { - val locale = getLocale() - - return translationsProvider.translate(key, locale, bundleName, replacements) - } - - /** - * Given a translation key and possible replacements,return the translation for the given locale in the - * extension's configured bundle, for the locale provided by the bot's configured locale resolvers. - */ - public suspend fun translate(key: String, replacements: Array = arrayOf()): String = translate( - key, - extension.bundle, - replacements - ) - - /** Convenience function to send a response follow-up message containing only text. **/ - public suspend fun respond(text: String): Any = when (isEphemeral) { - true -> ephemeralFollowUp { content = text } - false -> publicFollowUp { content = text } - - else -> interactionResponse = interaction.respondEphemeral { content = text } - } -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt deleted file mode 100644 index abfa4636ac..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/InteractiveButtonContext.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.kotlindiscord.kord.extensions.components.contexts - -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.interaction.* -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import org.koin.core.component.KoinComponent - -/** - * Context object representing the execution context of an interactive button interaction. - */ -@OptIn(KordPreview::class) -@ExtensionDSL -public open class InteractiveButtonContext( - extension: Extension, - event: ComponentInteractionCreateEvent, - components: Components, - interactionResponse: InteractionResponseBehavior? = null, - interaction: ButtonInteraction = event.interaction as ButtonInteraction, - sentryContext: SentryContext -) : KoinComponent, ActionableComponentContext( - extension, event, components, interactionResponse, interaction, sentryContext -) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt deleted file mode 100644 index 0799754d2f..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/contexts/MenuContext.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.kotlindiscord.kord.extensions.components.contexts - -import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.sentry.SentryContext -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.interaction.SelectMenuInteraction -import dev.kord.core.event.interaction.ComponentInteractionCreateEvent -import org.koin.core.component.KoinComponent -import java.util.* - -/** - * Context object representing the execution context of a menu selection interaction. - */ -@OptIn(KordPreview::class) -@ExtensionDSL -public open class MenuContext( - extension: Extension, - event: ComponentInteractionCreateEvent, - components: Components, - interactionResponse: InteractionResponseBehavior? = null, - interaction: SelectMenuInteraction = event.interaction as SelectMenuInteraction, - sentryContext: SentryContext -) : KoinComponent, ActionableComponentContext( - extension, event, components, interactionResponse, interaction, sentryContext -) { - /** Quick access to the selected values. **/ - public val selected: List = interaction.values -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt new file mode 100644 index 0000000000..effa0f95a3 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -0,0 +1,55 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder + +public typealias InitialEphemeralSelectMenuResponseBuilder = + (suspend EphemeralInteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +public open class EphemeralSelectMenu : SelectMenu() { + public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null + + override suspend fun call(event: SelectMenuInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondEphemeral { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondEphemeral { initialResponseBuilder!!(event) } + } else { + if (!deferredAck) { + event.interaction.acknowledgeEphemeralDeferredMessageUpdate() + } else { + event.interaction.acknowledgeEphemeral() + } + } + + val context = EphemeralSelectMenuContext(this, event, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText(context: EphemeralSelectMenuContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt new file mode 100644 index 0000000000..86a1d76d74 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +public class EphemeralSelectMenuContext( + override val component: Component, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: EphemeralInteractionResponseBehavior +) : SelectMenuContext(component, event), EphemeralInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt new file mode 100644 index 0000000000..587b9021dd --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -0,0 +1,55 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder + +public typealias InitialPublicSelectMenuResponseBuilder = + (suspend PublicInteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + +public open class PublicSelectMenu : SelectMenu() { + public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null + + override suspend fun call(event: SelectMenuInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = if (initialResponseBuilder != null) { + event.interaction.respondPublic { initialResponseBuilder!!(event) } + } else { + if (!deferredAck) { + event.interaction.acknowledgePublic() + } else { + event.interaction.acknowledgePublicDeferredMessageUpdate() + } + } + + val context = PublicSelectMenuContext(this, event, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText(context: PublicSelectMenuContext, message: String) { + context.respond { content = message } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt new file mode 100644 index 0000000000..3b5fd2b8b0 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +public class PublicSelectMenuContext( + override val component: Component, + override val event: SelectMenuInteractionCreateEvent, + override val interactionResponse: PublicInteractionResponseBehavior +) : SelectMenuContext(component, event), PublicInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt new file mode 100644 index 0000000000..100e2e5e3e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -0,0 +1,173 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.components.ComponentWithAction +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType +import com.kotlindiscord.kord.extensions.sentry.tag +import com.kotlindiscord.kord.extensions.sentry.user +import dev.kord.core.entity.channel.DmChannel +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kord.rest.builder.component.SelectOptionBuilder +import io.sentry.Sentry +import mu.KLogger +import mu.KotlinLogging + +/** Maximum length for an option's description. **/ +public const val DESCRIPTION_MAX: Int = 50 + +/** Maximum length for an option's label. **/ +public const val LABEL_MAX: Int = 25 + +/** Maximum number of options for a menu. **/ +public const val OPTIONS_MAX: Int = 25 + +/** Maximum length for a menu's placeholder. **/ +public const val PLACEHOLDER_MAX: Int = 100 + +/** Maximum length for an option's value. **/ +public const val VALUE_MAX: Int = 100 + +public abstract class SelectMenu : ComponentWithAction() { + internal val logger: KLogger = KotlinLogging.logger {} + + /** List of options for the user to choose from. **/ + public val options: MutableList = mutableListOf() + + /** The minimum number of choices that the user must make. **/ + public var minimumChoices: Int = 1 + + /** The maximum number of choices that the user can make. Set to `null` for no maximum. **/ + public var maximumChoices: Int? = 1 + + /** Placeholder text to show before the user has selected any options. **/ + public var placeholder: String? = null + + override val unitWidth: Int = 5 + + /** Add an option to this select menu. **/ + public open suspend fun option(label: String, value: String, body: suspend SelectOptionBuilder.() -> Unit = {}) { + val builder = SelectOptionBuilder(label, value) + + body(builder) + + if (builder.description?.length ?: 0 > DESCRIPTION_MAX) { + error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.") + } + + if (builder.label.length > LABEL_MAX) { + error("Option labels must not be longer than $LABEL_MAX characters.") + } + + if (builder.value.length > VALUE_MAX) { + error("Option values must not be longer than $VALUE_MAX characters.") + } + + options.add(builder) + } + + public override fun apply(builder: ActionRowBuilder) { + if (maximumChoices == null || maximumChoices!! > options.size) { + maximumChoices = options.size + } + + builder.selectMenu(id) { + allowedValues = minimumChoices..maximumChoices!! + + this.options.addAll(this@SelectMenu.options) + this.placeholder = this@SelectMenu.placeholder + } + } + + override fun validate() { + super.validate() + + if (this.options.isEmpty()) { + error("Menu components must have at least one option.") + } + + if (this.options.size > OPTIONS_MAX) { + error("Menu components must not have more than $OPTIONS_MAX options.") + } + + if (this.placeholder?.length ?: 0 > PLACEHOLDER_MAX) { + error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") + } + } + + /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ + public open suspend fun firstSentryBreadcrumb(context: C, button: SelectMenu<*>) { + if (sentry.enabled) { + context.sentry.breadcrumb(BreadcrumbType.User) { + category = "component.selectMenu" + message = "Select menu \"${button.id}\" submitted." + + val channel = context.channel.asChannelOrNull() + val guild = context.guild?.asGuildOrNull() + val message = context.message + + data["component"] = button.id + + if (channel != null) { + data["channel"] = when (channel) { + is DmChannel -> "Private Message (${channel.id.asString})" + is GuildMessageChannel -> "#${channel.name} (${channel.id.asString})" + + else -> channel.id.asString + } + } + + if (guild != null) { + data["guild"] = "${guild.name} (${guild.id.asString})" + } + + if (message != null) { + data["message"] = message.id.asString + } + } + } + } + + /** A general way to handle errors thrown during the course of a select menu action's execution. **/ + public open suspend fun handleError(context: C, t: Throwable, button: SelectMenu<*>) { + logger.error(t) { "Error during execution of select menu (${button.id}) action (${context.event})" } + + if (sentry.enabled) { + logger.debug { "Submitting error to sentry." } + + val channel = context.channel + val author = context.user.asUserOrNull() + + val sentryId = context.sentry.captureException(t, "Select menu action execution failed.") { + if (author != null) { + user(author) + } + + tag("private", "false") + + if (channel is DmChannel) { + tag("private", "true") + } + + tag("component", button.id) + + Sentry.captureException(t, "Select menu action execution failed.") + } + + logger.debug { "Error submitted to Sentry: $sentryId" } + + val errorMessage = if (bot.extensions.containsKey("sentry")) { + context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + } else { + context.translate("commands.error.user", null) + } + + respondText(context, errorMessage) + } else { + respondText(context, context.translate("commands.error.user", null)) + } + } + + /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ + public abstract suspend fun respondText(context: C, message: String) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt new file mode 100644 index 0000000000..00a7da7a22 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt @@ -0,0 +1,12 @@ +package com.kotlindiscord.kord.extensions.components.menus + +import com.kotlindiscord.kord.extensions.components.Component +import com.kotlindiscord.kord.extensions.components.ComponentContext +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent + +public abstract class SelectMenuContext( + component: Component, + event: SelectMenuInteractionCreateEvent +) : ComponentContext(component, event) { + public val selected: List = event.interaction.values +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt new file mode 100644 index 0000000000..2ff9f6d081 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt @@ -0,0 +1,53 @@ +package com.kotlindiscord.kord.extensions.components.types + +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.optional.optional +import dev.kord.core.entity.GuildEmoji +import dev.kord.core.entity.ReactionEmoji + +public interface HasPartialEmoji { + /** + * A partial emoji object, either a guild or Unicode emoji. Optional if you've got a label. + * + * @see emoji + */ + public var partialEmoji: DiscordPartialEmoji? +} + +/** Convenience function for setting [HasPartialEmoji.partialEmoji] based on a given Unicode emoji. **/ +public fun HasPartialEmoji.emoji(unicodeEmoji: String) { + partialEmoji = DiscordPartialEmoji( + name = unicodeEmoji + ) +} + +/** Convenience function for setting [HasPartialEmoji.partialEmoji] based on a given guild custom emoji. **/ +public fun HasPartialEmoji.emoji(guildEmoji: GuildEmoji) { + partialEmoji = DiscordPartialEmoji( + id = guildEmoji.id, + name = guildEmoji.name, + animated = guildEmoji.isAnimated.optional() + ) +} + +/** Convenience function for setting [HasPartialEmoji.partialEmoji] based on a given reaction emoji. **/ +public fun HasPartialEmoji.emoji(unicodeEmoji: ReactionEmoji.Unicode) { + partialEmoji = DiscordPartialEmoji( + name = unicodeEmoji.name + ) +} + +/** Convenience function for setting [HasPartialEmoji.partialEmoji] based on a given reaction emoji. **/ +public fun HasPartialEmoji.emoji(guildEmoji: ReactionEmoji.Custom) { + partialEmoji = DiscordPartialEmoji( + id = guildEmoji.id, + name = guildEmoji.name, + animated = guildEmoji.isAnimated.optional() + ) +} + +/** Convenience function for setting [HasPartialEmoji.partialEmoji] based on a given reaction emoji. **/ +public fun HasPartialEmoji.emoji(emoji: ReactionEmoji): Unit = when (emoji) { + is ReactionEmoji.Unicode -> emoji(emoji) + is ReactionEmoji.Custom -> emoji(emoji) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt index 3e72af5db8..6788d7f612 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/HelpExtension.kt @@ -149,7 +149,6 @@ public class HelpExtension : HelpProvider, Extension() { } return MessageButtonPaginator( - extension = this, keepEmbed = settings.deletePaginatorOnTimeout.not(), locale = locale, owner = event.message.author, @@ -235,7 +234,6 @@ public class HelpExtension : HelpProvider, Extension() { } return MessageButtonPaginator( - extension = this, keepEmbed = settings.deletePaginatorOnTimeout.not(), locale = locale, owner = event.message.author, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt index 66fa311cf6..7914edc41e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/impl/SentryExtension.kt @@ -4,12 +4,12 @@ package com.kotlindiscord.kord.extensions.extensions.impl import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescedString import com.kotlindiscord.kord.extensions.commands.converters.impl.string import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.chatCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand +import com.kotlindiscord.kord.extensions.interactions.respond import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.sentryId import com.kotlindiscord.kord.extensions.utils.respond diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt similarity index 53% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt index 98dabe1ea7..3597133b01 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/EphemeralApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt @@ -1,14 +1,17 @@ -package com.kotlindiscord.kord.extensions.commands.application +package com.kotlindiscord.kord.extensions.interactions +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUpEphemeral import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder +import java.util.* /** Interface representing an ephemeral-only application command context. **/ -public interface EphemeralApplicationCommandContext { +public interface EphemeralInteractionContext { /** Response created by acknowledging the interaction ephemerally. **/ public val interactionResponse: EphemeralInteractionResponseBehavior } @@ -18,19 +21,25 @@ public interface EphemeralApplicationCommandContext { * * **Note:** Calling this twice will result in a public followup! */ -public suspend inline fun EphemeralApplicationCommandContext.respond( +public suspend inline fun EphemeralInteractionContext.respond( builder: EphemeralFollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) /** - * Edit the current interaction's response, if one was sent via `initialResponse`. + * Edit the current interaction's response. */ -public suspend inline fun EphemeralApplicationCommandContext.edit( +public suspend inline fun EphemeralInteractionContext.edit( builder: EphemeralInteractionResponseModifyBuilder.() -> Unit -) { - val response = interactionResponse as? EphemeralInteractionResponseBehavior ?: error( - "Unable to edit: No `initialResponse` builder was provided." - ) +): Unit = interactionResponse.edit(builder) - response.edit(builder) +public suspend inline fun EphemeralInteractionContext.editingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): EphemeralResponsePaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return EphemeralResponsePaginator(pages, interactionResponse) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt new file mode 100644 index 0000000000..342860fe3e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt @@ -0,0 +1,55 @@ +package com.kotlindiscord.kord.extensions.interactions + +import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator +import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit +import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.entity.Message +import dev.kord.core.entity.interaction.PublicFollowupMessage +import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder +import java.util.* + +/** Interface representing a public-only application command context. **/ +public interface PublicInteractionContext { + /** Response created by acknowledging the interaction publicly. **/ + public val interactionResponse: PublicInteractionResponseBehavior +} + +/** Respond to the current interaction with a public followup. **/ +public suspend inline fun PublicInteractionContext.respond( + builder: PublicFollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage = interactionResponse.followUp(builder) + +/** + * Edit the current interaction's response. + */ +public suspend inline fun PublicInteractionContext.edit( + builder: PublicInteractionResponseModifyBuilder.() -> Unit +): Message = interactionResponse.edit(builder) + +public suspend inline fun PublicInteractionContext.editingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): PublicResponsePaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return PublicResponsePaginator(pages, interactionResponse) +} + +public suspend inline fun PublicInteractionContext.respondingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): PublicFollowUpPaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return PublicFollowUpPaginator(pages, interactionResponse) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index ca3391a182..a17f9a5fc6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -3,12 +3,15 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.components.builders.DisabledButtonBuilder -import com.kotlindiscord.kord.extensions.components.builders.InteractiveButtonBuilder -import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.components.ComponentContainer +import com.kotlindiscord.kord.extensions.components.buttons.DisabledInteractionButton +import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButton +import com.kotlindiscord.kord.extensions.components.publicButton +import com.kotlindiscord.kord.extensions.components.types.emoji import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.utils.capitalizeWords +import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler +import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ButtonStyle import dev.kord.core.entity.ReactionEmoji @@ -16,17 +19,10 @@ import dev.kord.core.entity.User import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import java.util.* -/** Last row number. **/ -public const val LAST_ROW: Int = 4 - -/** Second row number. **/ -public const val SECOND_ROW: Int = 1 - /** * Abstract class containing some common functionality needed by interactive button-based paginators. */ public abstract class BaseButtonPaginator( - extension: Extension, pages: Pages, owner: User? = null, timeoutSeconds: Long? = null, @@ -34,27 +30,40 @@ public abstract class BaseButtonPaginator( switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, bundle: String? = null, locale: Locale? = null, -) : BasePaginator(extension, pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { - /** [Components] instance managing the buttons for this paginator. **/ - public abstract var components: Components +) : BasePaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { + /** [ComponentContainer] instance managing the buttons for this paginator. **/ + public open var components: ComponentContainer = ComponentContainer() + + /** Scheduler used to schedule the paginator's timeout. **/ + public var scheduler: Scheduler = Scheduler() + + /** Scheduler used to schedule the paginator's timeout. **/ + public var task: Task? = if (timeoutSeconds != null) { + scheduler.schedule(timeoutSeconds) { destroy() } + } else { + null + } + + private val lastRowNumber = components.rows.size - 1 + private val secondRowNumber = 1 /** Button builder representing the button that switches to the first page. **/ - public open var firstPageButton: InteractiveButtonBuilder? = null + public open var firstPageButton: PublicInteractionButton? = null /** Button builder representing the button that switches to the previous page. **/ - public open var backButton: InteractiveButtonBuilder? = null + public open var backButton: PublicInteractionButton? = null /** Button builder representing the button that switches to the next page. **/ - public open var nextButton: InteractiveButtonBuilder? = null + public open var nextButton: PublicInteractionButton? = null /** Button builder representing the button that switches to the last page. **/ - public open var lastPageButton: InteractiveButtonBuilder? = null + public open var lastPageButton: PublicInteractionButton? = null /** Button builder representing the button that switches between groups. **/ - public open var switchButton: InteractiveButtonBuilder? = null + public open var switchButton: PublicInteractionButton? = null /** Group-specific buttons, if any. **/ - public open val groupButtons: MutableMap = mutableMapOf() + public open val groupButtons: MutableMap = mutableMapOf() /** Whether it's possible for us to have a row of group-switching buttons. **/ @Suppress("MagicNumber") @@ -73,14 +82,15 @@ public abstract class BaseButtonPaginator( } } - override suspend fun setup() { - components.onTimeout { - destroy() - } + override suspend fun destroy() { + task?.cancel() + runTimeoutCallbacks() + } + override suspend fun setup() { if (pages.size > 1) { // Add navigation buttons... - firstPageButton = components.interactiveButton { + firstPageButton = components.publicButton { deferredAck = true style = ButtonStyle.Secondary @@ -92,10 +102,11 @@ public abstract class BaseButtonPaginator( goToPage(0) send() + task?.restart() } } - backButton = components.interactiveButton { + backButton = components.publicButton { deferredAck = true style = ButtonStyle.Secondary @@ -107,10 +118,11 @@ public abstract class BaseButtonPaginator( previousPage() send() + task?.restart() } } - nextButton = components.interactiveButton { + nextButton = components.publicButton { deferredAck = true style = ButtonStyle.Secondary @@ -122,10 +134,11 @@ public abstract class BaseButtonPaginator( nextPage() send() + task?.restart() } } - lastPageButton = components.interactiveButton { + lastPageButton = components.publicButton { deferredAck = true style = ButtonStyle.Secondary @@ -137,13 +150,14 @@ public abstract class BaseButtonPaginator( goToPage(pages.size - 1) send() + task?.restart() } } } if (pages.size > 1 || !keepEmbed) { // Add the destroy button - components.interactiveButton(LAST_ROW) { + components.publicButton(lastRowNumber) { deferredAck = true check(defaultCheck) @@ -171,7 +185,7 @@ public abstract class BaseButtonPaginator( // Add group-switching buttons allGroups.forEach { group -> - groupButtons[group] = components.interactiveButton(SECOND_ROW) { + groupButtons[group] = components.publicButton(secondRowNumber) { deferredAck = true label = translate(group).capitalizeWords(localeObj) style = ButtonStyle.Secondary @@ -180,13 +194,14 @@ public abstract class BaseButtonPaginator( action { switchGroup(group) + task?.restart() } } } } else { // Add the singular switch button - switchButton = components.interactiveButton(LAST_ROW) { + switchButton = components.publicButton(lastRowNumber) { deferredAck = true check(defaultCheck) @@ -203,12 +218,13 @@ public abstract class BaseButtonPaginator( nextGroup() send() + task?.restart() } } } } - components.sortIntoRows() + components.sort() updateButtons() } @@ -296,10 +312,10 @@ public abstract class BaseButtonPaginator( } /** Replace an enabled interactive button in [components] with a disabled button of the same ID. **/ - public fun setDisabledButton(oldButton: InteractiveButtonBuilder?): Boolean { + public fun setDisabledButton(oldButton: PublicInteractionButton?): Boolean { oldButton ?: return false - val newButton = DisabledButtonBuilder() + val newButton = DisabledInteractionButton() // Copy properties from the old button newButton.id = oldButton.id @@ -307,35 +323,13 @@ public abstract class BaseButtonPaginator( newButton.partialEmoji = oldButton.partialEmoji newButton.style = oldButton.style - // Find the old button and replace it - components.rows.forEach { row -> - val index = row.indexOfFirst { it is InteractiveButtonBuilder && it.id == oldButton.id } - - if (index != -1) { - row[index] = newButton - - return true - } - } - - return false + return components.replace(oldButton, newButton) } /** Replace a disabled button in [components] with the given interactive button of the same ID.. **/ - public fun setEnabledButton(newButton: InteractiveButtonBuilder?): Boolean { + public fun setEnabledButton(newButton: PublicInteractionButton?): Boolean { newButton ?: return false - // Find the disabled button and replace it - components.rows.forEach { row -> - val index = row.indexOfFirst { it is DisabledButtonBuilder && it.id == newButton.id } - - if (index != -1) { - row[index] = newButton - - return true - } - } - - return false + return components.replace(newButton.id, newButton) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt index 1631a5b9d5..e81a9ee9e0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt @@ -1,7 +1,6 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages @@ -43,7 +42,6 @@ public val EXPAND_EMOJI: ReactionEmoji.Unicode = ReactionEmoji.Unicode("\u2139\u * * **Note:** This is going to be renamed - it's not ready for use yet! * - * @param extension Extension that this paginator was created for * @param pages Pages object containing this paginator's pages * @param owner Optional paginator owner - setting this will prevent other users from interacting with the paginator * @param timeoutSeconds How long (in seconds) to wait before destroying the paginator, if needed @@ -53,7 +51,6 @@ public val EXPAND_EMOJI: ReactionEmoji.Unicode = ReactionEmoji.Unicode("\u2139\u * @param bundle Translation bundle to use for this paginator */ public abstract class BasePaginator( - public open val extension: Extension, public open val pages: Pages, public open val owner: User? = null, public open val timeoutSeconds: Long? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt new file mode 100644 index 0000000000..95c85bd063 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt @@ -0,0 +1,81 @@ +@file:OptIn(KordPreview::class) + +package com.kotlindiscord.kord.extensions.pagination + +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit +import dev.kord.core.entity.ReactionEmoji +import dev.kord.core.entity.User +import dev.kord.rest.builder.message.modify.embed +import java.util.* + +/** + * Class representing a button-based paginator that operates by editing the given ephemeral interaction response. + * + * @param interaction Interaction response behaviour to work with. + */ +public class EphemeralResponsePaginator( + pages: Pages, + owner: User? = null, + timeoutSeconds: Long? = null, + switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, + bundle: String? = null, + locale: Locale? = null, + + public val interaction: EphemeralInteractionResponseBehavior, +) : BaseButtonPaginator(pages, owner, timeoutSeconds, true, switchEmoji, bundle, locale) { + public var isSetup: Boolean = false + + override suspend fun send() { + if (!isSetup) { + isSetup = true + + setup() + } else { + updateButtons() + } + + interaction.edit { + embed { applyPage() } + + with(this@EphemeralResponsePaginator.components) { + this@edit.applyToMessage() + } + } + } + + override suspend fun destroy() { + super.destroy() + + if (!active) { + return + } + + active = false + + interaction.edit { + embed { applyPage() } + + this.components = mutableListOf() + } + } +} + +/** Convenience function for creating an interaction button paginator from a paginator builder. **/ +@Suppress("FunctionNaming") // Factory function +public fun EphemeralResponsePaginator( + builder: PaginatorBuilder, + interaction: EphemeralInteractionResponseBehavior +): EphemeralResponsePaginator = EphemeralResponsePaginator( + pages = builder.pages, + owner = builder.owner, + timeoutSeconds = builder.timeoutSeconds, + bundle = builder.bundle, + locale = builder.locale, + interaction = interaction, + + switchEmoji = builder.switchEmoji ?: if (builder.pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, +) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt index a9df0d45dd..658180ae8c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt @@ -2,8 +2,6 @@ package com.kotlindiscord.kord.extensions.pagination -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview @@ -27,7 +25,6 @@ import java.util.* * @param targetChannel Target channel to send the paginator to, if [targetMessage] isn't provided. */ public class MessageButtonPaginator( - extension: Extension, pages: Pages, owner: User? = null, timeoutSeconds: Long? = null, @@ -39,15 +36,13 @@ public class MessageButtonPaginator( public val pingInReply: Boolean = true, public val targetChannel: MessageChannelBehavior? = null, public val targetMessage: Message? = null, -) : BaseButtonPaginator(extension, pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { +) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { init { if (targetChannel == null && targetMessage == null) { throw IllegalArgumentException("Must provide either a target channel or target message") } } - override var components: Components = Components(extension) - /** Specific channel to send the paginator to. **/ public val channel: MessageChannelBehavior = targetMessage?.channel ?: targetChannel!! @@ -55,8 +50,6 @@ public class MessageButtonPaginator( public var message: Message? = null override suspend fun send() { - components.stop() - if (message == null) { setup() @@ -67,7 +60,7 @@ public class MessageButtonPaginator( embed { applyPage() } with(this@MessageButtonPaginator.components) { - this@createMessage.setup(timeoutSeconds) + this@createMessage.applyToMessage() } } } else { @@ -77,13 +70,15 @@ public class MessageButtonPaginator( embed { applyPage() } with(this@MessageButtonPaginator.components) { - this@edit.setup(timeoutSeconds) + this@edit.applyToMessage() } } } } override suspend fun destroy() { + super.destroy() + if (!active) { return } @@ -102,7 +97,6 @@ public class MessageButtonPaginator( } runTimeoutCallbacks() - components.stop() } } @@ -116,7 +110,6 @@ public fun MessageButtonPaginator( builder: PaginatorBuilder ): MessageButtonPaginator = MessageButtonPaginator( - extension = builder.extension, pages = builder.pages, owner = builder.owner, timeoutSeconds = builder.timeoutSeconds, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt similarity index 51% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt index 2dbaa4ff30..5618242b5d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/InteractionButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt @@ -2,14 +2,12 @@ package com.kotlindiscord.kord.extensions.pagination -import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext -import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext -import com.kotlindiscord.kord.extensions.components.Components -import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit +import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.ReactionEmoji import dev.kord.core.entity.User import dev.kord.core.entity.interaction.PublicFollowupMessage @@ -17,14 +15,14 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed import java.util.* + /** - * Class representing a button-based paginator that operates on public-acked interactions. Essentially, use this with - * slash commands. + * Class representing a button-based paginator that operates by creating and editing a follow-up message for the + * given public interaction response. * - * @param parentContext Parent slash command context to be worked with. + * @param interaction Interaction response behaviour to work with. */ -public class InteractionButtonPaginator( - extension: Extension, +public class PublicFollowUpPaginator( pages: Pages, owner: User? = null, timeoutSeconds: Long? = null, @@ -33,48 +31,37 @@ public class InteractionButtonPaginator( bundle: String? = null, locale: Locale? = null, - public val parentContext: SlashCommandContext<*, *>, -) : BaseButtonPaginator(extension, pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { - init { - if (parentContext is EphemeralSlashCommandContext<*>) { - error("Paginators cannot operate with ephemeral interactions.") - } - } - - override var components: Components = Components(extension, parentContext) - - /** Follow-up message containing all of the buttons. **/ + public val interaction: PublicInteractionResponseBehavior, +) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { public var embedInteraction: PublicFollowupMessage? = null override suspend fun send() { - components.stop() - if (embedInteraction == null) { setup() - TODO() + embedInteraction = interaction.followUp { + embed { applyPage() } -// embedInteraction = parentContext.respond { -// embed { applyPage() } -// -// with(this@InteractionButtonPaginator.components) { -// this@publicFollowUp.setup(timeoutSeconds) -// } -// } + with(this@PublicFollowUpPaginator.components) { + this@followUp.applyToMessage() + } + } } else { updateButtons() embedInteraction!!.edit { embed { applyPage() } - with(this@InteractionButtonPaginator.components) { - this@edit.setup(timeoutSeconds) + with(this@PublicFollowUpPaginator.components) { + this@edit.applyToMessage() } } } } override suspend fun destroy() { + super.destroy() + if (!active) { return } @@ -82,34 +69,30 @@ public class InteractionButtonPaginator( active = false if (!keepEmbed) { - embedInteraction!!.delete() + embedInteraction?.delete() } else { - embedInteraction!!.edit { + embedInteraction?.edit { embed { applyPage() } this.components = mutableListOf() } } - - runTimeoutCallbacks() - components.stop() } } /** Convenience function for creating an interaction button paginator from a paginator builder. **/ @Suppress("FunctionNaming") // Factory function -public fun InteractionButtonPaginator( +public fun PublicFollowUpPaginator( builder: PaginatorBuilder, - parentContext: SlashCommandContext<*, *> -): InteractionButtonPaginator = InteractionButtonPaginator( - extension = builder.extension, + interaction: PublicInteractionResponseBehavior +): PublicFollowUpPaginator = PublicFollowUpPaginator( pages = builder.pages, owner = builder.owner, timeoutSeconds = builder.timeoutSeconds, keepEmbed = builder.keepEmbed, bundle = builder.bundle, locale = builder.locale, - parentContext = parentContext, + interaction = interaction, switchEmoji = builder.switchEmoji ?: if (builder.pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt new file mode 100644 index 0000000000..08af2f7e3e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt @@ -0,0 +1,83 @@ +@file:OptIn(KordPreview::class) + +package com.kotlindiscord.kord.extensions.pagination + +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit +import dev.kord.core.entity.ReactionEmoji +import dev.kord.core.entity.User +import dev.kord.rest.builder.message.modify.embed +import java.util.* + +/** + * Class representing a button-based paginator that operates by editing the given public interaction response. + * + * @param interaction Interaction response behaviour to work with. + */ +public class PublicResponsePaginator( + pages: Pages, + owner: User? = null, + timeoutSeconds: Long? = null, + keepEmbed: Boolean = true, + switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, + bundle: String? = null, + locale: Locale? = null, + + public val interaction: PublicInteractionResponseBehavior, +) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { + public var isSetup: Boolean = false + + override suspend fun send() { + if (!isSetup) { + isSetup = true + + setup() + } else { + updateButtons() + } + + interaction.edit { + embed { applyPage() } + + with(this@PublicResponsePaginator.components) { + this@edit.applyToMessage() + } + } + } + + override suspend fun destroy() { + super.destroy() + + if (!active) { + return + } + + active = false + + interaction.edit { + embed { applyPage() } + + this.components = mutableListOf() + } + } +} + +/** Convenience function for creating an interaction button paginator from a paginator builder. **/ +@Suppress("FunctionNaming") // Factory function +public fun PublicResponsePaginator( + builder: PaginatorBuilder, + interaction: PublicInteractionResponseBehavior +): PublicResponsePaginator = PublicResponsePaginator( + pages = builder.pages, + owner = builder.owner, + timeoutSeconds = builder.timeoutSeconds, + keepEmbed = builder.keepEmbed, + bundle = builder.bundle, + locale = builder.locale, + interaction = interaction, + + switchEmoji = builder.switchEmoji ?: if (builder.pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, +) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt new file mode 100644 index 0000000000..56b5534620 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt @@ -0,0 +1,42 @@ +package com.kotlindiscord.kord.extensions.pagination + +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import java.util.* + +public suspend inline fun PublicInteractionResponseBehavior.editingPaginator( + locale: Locale? = null, + defaultGroup: String = "", + builder: (PaginatorBuilder).() -> Unit +): PublicResponsePaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return PublicResponsePaginator(pages, this) +} + +public suspend inline fun PublicInteractionResponseBehavior.respondingPaginator( + locale: Locale? = null, + defaultGroup: String = "", + builder: (PaginatorBuilder).() -> Unit +): PublicFollowUpPaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return PublicFollowUpPaginator(pages, this) +} + +public suspend inline fun EphemeralInteractionResponseBehavior.editingPaginator( + locale: Locale? = null, + defaultGroup: String = "", + builder: (PaginatorBuilder).() -> Unit +): EphemeralResponsePaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return EphemeralResponsePaginator(pages, this) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt index 3102b26216..bc4c6e6686 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt @@ -1,6 +1,5 @@ package com.kotlindiscord.kord.extensions.pagination.builders -import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.core.entity.ReactionEmoji @@ -16,7 +15,6 @@ import java.util.* * @param defaultGroup Default page group, if any */ public class PaginatorBuilder( - public val extension: Extension, public var locale: Locale? = null, public val defaultGroup: String = "" ) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt index 5640691a0f..6b2b138b9e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/scheduling/Task.kt @@ -87,7 +87,7 @@ public class Task( removeFromParent() } - /** Like [cancel], but blocks until the cancellation has been applied.. **/ + /** Like [cancel], but blocks .. **/ public suspend fun cancelAndJoin() { job?.cancelAndJoin() job = null @@ -95,6 +95,22 @@ public class Task( removeFromParent() } + /** If the task is running, cancel it and restart it. **/ + public fun restart() { + job?.cancel() + job = null + + start() + } + + /** Like [restart], but blocks until the cancellation has been applied. **/ + public suspend fun restartJoining() { + job?.cancelAndJoin() + job = null + + start() + } + /** Join the running [job], if any. **/ public suspend fun join(): Unit? = job?.join() diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 0fcd523aca..8b9fa2748f 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -3,19 +3,22 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.commands.application.respond import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.enumChoice import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand import com.kotlindiscord.kord.extensions.commands.application.slash.group import com.kotlindiscord.kord.extensions.commands.application.slash.publicSubCommand import com.kotlindiscord.kord.extensions.commands.converters.impl.* -import com.kotlindiscord.kord.extensions.components.AutoAckType +import com.kotlindiscord.kord.extensions.components.* +import com.kotlindiscord.kord.extensions.components.types.emoji import com.kotlindiscord.kord.extensions.extensions.* +import com.kotlindiscord.kord.extensions.interactions.editingPaginator +import com.kotlindiscord.kord.extensions.interactions.respond import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.annotation.KordPreview +import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.reply @@ -164,9 +167,8 @@ class TestExtension : Extension() { message.respond { content = "Here's a dropdown." - components(60) { - menu { - autoAck = AutoAckType.PUBLIC + components { + publicSelectMenu { maximumChoices = null option("Option 1", "one") @@ -174,7 +176,7 @@ class TestExtension : Extension() { option("Option 3", "three") action { - publicFollowUp { + respond { content = "You picked the following options: " + selected.joinToString { "`$it`" } @@ -186,59 +188,50 @@ class TestExtension : Extension() { } } - publicSlashCommand { + ephemeralSlashCommand { name = "pages" description = "Pages!" guild(787452339908116521) action { -// paginator("short") { -// owner = event.interaction.user.asUser() -// timeoutSeconds = 60 -// keepEmbed = false -// -// (0..2).forEach { -// page( -// Page { -// description = "Short page $it." -// -// footer { -// text = "Footer text ($it)" -// } -// } -// ) -// -// page( -// "Expanded", -// -// Page { -// description = "Expanded page $it, expanded page $it\n" + -// "Expanded page $it, expanded page $it" -// -// footer { -// text = "Footer text ($it)" -// } -// } -// ) -// -// page( -// "MASSIVE GROUP", -// -// Page { -// description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + -// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + -// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + -// "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + -// "MASSIVE PAGE $it, MASSIVE PAGE $it" -// -// footer { -// text = "Footer text ($it)" -// } -// } -// ) -// } -// }.send() + editingPaginator("short") { + owner = event.interaction.user.asUser() + timeoutSeconds = 60 + keepEmbed = false + + (0..2).forEach { + page { + description = "Short page $it." + + footer { + text = "Footer text ($it)" + } + } + + + page("Expanded") { + description = "Expanded page $it, expanded page $it\n" + + "Expanded page $it, expanded page $it" + + footer { + text = "Footer text ($it)" + } + } + + page("MASSIVE GROUP") { + description = "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it\n" + + "MASSIVE PAGE $it, MASSIVE PAGE $it" + + footer { + text = "Footer text ($it)" + } + } + } + }.send() } } @@ -252,35 +245,35 @@ class TestExtension : Extension() { respond { content = "Buttons!" -// components(60) { -// interactiveButton { -// label = "Button one!" -// -// action { -// respond("Button one pressed!") -// } -// } -// -// interactiveButton { -// label = "Button two!" -// style = ButtonStyle.Secondary -// -// action { -// respond("Button two pressed!") -// } -// } -// -// disabledButton { -// emoji("❎") -// } -// -// linkButton { -// label = "Google" -// emoji("🔗") -// -// url = "https://google.com" -// } -// } + components { + ephemeralButton { + label = "Button one!" + + action { + respond { content = "Button one pressed!" } + } + } + + ephemeralButton { + label = "Button two!" + style = ButtonStyle.Secondary + + action { + respond { content = "Button two pressed!" } + } + } + + disabledButton { + emoji("❎") + } + + linkButton { + label = "Google" + emoji("🔗") + + url = "https://google.com" + } + } } } } @@ -621,7 +614,6 @@ class TestExtension : Extension() { } MessageButtonPaginator( - extension = this@TestExtension, targetMessage = event.message, pages = pages, keepEmbed = false, From e7376639a13ea55a352691dad9b0b649e8e5b988 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 6 Sep 2021 22:13:53 +0100 Subject: [PATCH 024/131] Misc fixes/cleanup and detekt linting. --- .../extra/mappings/MappingsExtension.kt | 9 ----- .../message/EphemeralMessageCommand.kt | 2 +- .../message/PublicMessageCommand.kt | 2 +- .../slash/EphemeralSlashCommand.kt | 2 +- .../application/slash/PublicSlashCommand.kt | 2 +- .../application/user/EphemeralUserCommand.kt | 2 +- .../application/user/PublicUserCommand.kt | 2 +- .../kord/extensions/components/Component.kt | 4 ++ .../components/ComponentContainer.kt | 40 ++++++++++++++++--- .../extensions/components/ComponentContext.kt | 9 ++++- .../components/ComponentRegistry.kt | 19 ++++++--- .../components/ComponentWithAction.kt | 15 ++++--- .../extensions/components/ComponentWithID.kt | 2 + .../kord/extensions/components/_Functions.kt | 16 ++++++++ .../buttons/DisabledInteractionButton.kt | 2 + .../buttons/EphemeralInteractionButton.kt | 11 +++++ .../EphemeralInteractionButtonContext.kt | 1 + .../components/buttons/InteractionButton.kt | 3 ++ .../buttons/InteractionButtonContext.kt | 1 + .../buttons/InteractionButtonWithAction.kt | 7 +++- .../buttons/InteractionButtonWithID.kt | 2 + .../buttons/LinkInteractionButton.kt | 2 + .../buttons/PublicInteractionButton.kt | 11 +++++ .../buttons/PublicInteractionButtonContext.kt | 1 + .../components/menus/EphemeralSelectMenu.kt | 9 +++++ .../menus/EphemeralSelectMenuContext.kt | 1 + .../components/menus/PublicSelectMenu.kt | 9 +++++ .../menus/PublicSelectMenuContext.kt | 1 + .../extensions/components/menus/SelectMenu.kt | 8 +++- .../components/menus/SelectMenuContext.kt | 4 +- .../components/types/HasPartialEmoji.kt | 4 ++ .../EphemeralInteractionContext.kt | 6 ++- .../interactions/PublicInteractionContext.kt | 4 +- .../pagination/EphemeralResponsePaginator.kt | 1 + .../pagination/PublicFollowUpPaginator.kt | 2 +- .../pagination/PublicResponsePaginator.kt | 1 + .../kord/extensions/pagination/_Functions.kt | 6 +++ .../kord/extensions/test/bot/TestExtension.kt | 1 - 38 files changed, 184 insertions(+), 40 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index 9c856bb3ce..1468675454 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -518,7 +518,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -583,7 +582,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -652,7 +650,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -718,7 +715,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -812,7 +808,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -878,7 +873,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -994,7 +988,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -1103,7 +1096,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, @@ -1212,7 +1204,6 @@ class MappingsExtension : Extension() { } val paginator = MessageButtonPaginator( - extension = this@MappingsExtension, targetMessage = event.message, pages = pagesObj, keepEmbed = true, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index d0faa0c2a3..5f906e0f60 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -19,7 +19,7 @@ public class EphemeralMessageCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialEphemeralMessageResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialEphemeralMessageResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index ff313e64cb..66dba13c37 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -19,7 +19,7 @@ public class PublicMessageCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialPublicMessageResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialPublicMessageResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index 572598176d..c733b702e6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -26,7 +26,7 @@ public class EphemeralSlashCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialEphemeralChatResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialEphemeralChatResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 221edd7de5..c0a6d9afd7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -26,7 +26,7 @@ public class PublicSlashCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialPublicChatResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialPublicChatResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 422d5b3239..6de75978d3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -19,7 +19,7 @@ public class EphemeralUserCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialEphemeralUserResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialEphemeralUserResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 40a496c547..41662f4fa6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -19,7 +19,7 @@ public class PublicUserCommand( /** @suppress Internal guilder **/ public var initialResponseBuilder: InitialPublicUserResponseBuilder = null - /** Call this tn open with a response, omit it to ack instead. **/ + /** Call this to open with a response, omit it to ack instead. **/ public fun initialResponse(body: InitialPublicUserResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt index 752496eb54..a234934b18 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/Component.kt @@ -10,6 +10,7 @@ import dev.kord.rest.builder.component.ActionRowBuilder import org.koin.core.component.KoinComponent import org.koin.core.component.inject +/** Abstract class representing a basic Discord component. **/ public abstract class Component : KoinComponent { /** Component width, how many "slots" in one row it needs to be added to the row. **/ public open val unitWidth: Int = 1 @@ -32,9 +33,12 @@ public abstract class Component : KoinComponent { /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() + /** Translation bundle, to retrieve translations from. **/ public open var bundle: String? = null + /** Validation function, called to ensure the component is valid, throws exceptions if not. **/ public abstract fun validate() + /** Called to apply the given component to a Kord [ActionRowBuilder]. **/ public abstract fun apply(builder: ActionRowBuilder) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt index 252855582a..aa0c81ebf5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt @@ -1,3 +1,5 @@ +@file:Suppress("AnnotationSpacing") // Genuinely hate having to deal with this one sometimes. + package com.kotlindiscord.kord.extensions.components import dev.kord.rest.builder.message.create.MessageCreateBuilder @@ -7,13 +9,17 @@ import dev.kord.rest.builder.message.modify.actionRow import org.koin.core.component.KoinComponent import org.koin.core.component.inject +/** The maximum number of slots you can have in a row. **/ public const val ROW_SIZE: Int = 5 +/** Class representing a single set of components that can be applied to any message. **/ public open class ComponentContainer : KoinComponent { internal val registry: ComponentRegistry by inject() + /** Components that haven't been sorted into rows by [sort] yet. **/ public open val unsortedComponents: MutableList = mutableListOf() + /** Array containing sorted rows of components. **/ public open val rows: Array> = arrayOf( // Up to 5 rows of components @@ -24,6 +30,7 @@ public open class ComponentContainer : KoinComponent { mutableListOf(), ) + /** Remove all components, and unregister them from the [ComponentRegistry]. **/ public open fun removeAll() { rows.toList().flatten().forEach { component -> if (component is ComponentWithID) { @@ -34,6 +41,7 @@ public open class ComponentContainer : KoinComponent { rows.forEach { it.clear() } } + /** Remove the given component, and unregister it from the [ComponentRegistry]. **/ public open fun remove(component: Component): Boolean { if (rows.any { it.remove(component) }) { if (component is ComponentWithID) { @@ -46,7 +54,8 @@ public open class ComponentContainer : KoinComponent { return false } - public open fun replace(old: Component, new: ComponentWithID): Boolean { + /** Given two components, replace the old component with the new one and likewise handle registration. **/ + public open fun replace(old: Component, new: Component): Boolean { for (row in rows) { val index = row.indexOf(old) @@ -54,6 +63,7 @@ public open class ComponentContainer : KoinComponent { continue } + @Suppress("UnnecessaryParentheses") // Yeah, but let me be paranoid. Please. val freeSlots = (ROW_SIZE - row.size) + old.unitWidth if (new.unitWidth > freeSlots) { @@ -64,7 +74,10 @@ public open class ComponentContainer : KoinComponent { } row[index] = new - registry.register(new) + + if (new is ComponentWithID) { + registry.register(new) + } return true } @@ -72,7 +85,11 @@ public open class ComponentContainer : KoinComponent { return false } - public open fun replace(id: String, new: ComponentWithID): Boolean { + /** + * Given an old component ID and new component, replace the old component with the new one and likewise handle + * registration. + */ + public open fun replace(id: String, new: Component): Boolean { for (row in rows) { val index = row.indexOfFirst { it is ComponentWithID && it.id == id } @@ -81,7 +98,7 @@ public open class ComponentContainer : KoinComponent { } val old = row[index] - val freeSlots = (ROW_SIZE - row.size) + old.unitWidth + val freeSlots = old.unitWidth + (ROW_SIZE - row.size) if (new.unitWidth > freeSlots) { error( @@ -91,7 +108,10 @@ public open class ComponentContainer : KoinComponent { } row[index] = new - registry.register(new) + + if (new is ComponentWithID) { + registry.register(new) + } return true } @@ -99,6 +119,10 @@ public open class ComponentContainer : KoinComponent { return false } + /** + * Add a component. New components will be unsorted, or placed in the numbered row denoted by [rowNum] if + * possible. + */ public open fun add(component: Component, rowNum: Int? = null) { component.validate() @@ -134,11 +158,13 @@ public open class ComponentContainer : KoinComponent { } } + /** Sort all components in [unsortedComponents] by packing them into rows as tightly as possible. **/ public open fun sort() { while (unsortedComponents.isNotEmpty()) { val component = unsortedComponents.removeFirst() var sorted = false + @Suppress("UnconditionalJumpStatementInLoop") // Yes, but this is nicer to read for (row in rows) { if (row.size >= ROW_SIZE || row.size + component.unitWidth > ROW_SIZE) { continue @@ -163,6 +189,7 @@ public open class ComponentContainer : KoinComponent { } } + /** Apply the components in this container to a message that's being created. **/ public open fun MessageCreateBuilder.applyToMessage() { sort() @@ -173,6 +200,7 @@ public open class ComponentContainer : KoinComponent { } } + /** Apply the components in this container to a message that's being edited. **/ public open fun MessageModifyBuilder.applyToMessage() { sort() @@ -184,6 +212,8 @@ public open class ComponentContainer : KoinComponent { } } +/** DSL-style factory function to make component containers these by hand easier. **/ +@Suppress("FunctionNaming") // It's a factory function, detekt... public suspend fun ComponentContainer(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { val container = ComponentContainer() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt index 57cbb7adba..a9e42b58ae 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt @@ -17,6 +17,13 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* +/** + * Abstract class representing the execution context for a generic components. + * + * @param E Event type the component makes use of + * @param component Component object that's being interacted with + * @param event Event that triggered this execution context + */ public abstract class ComponentContext( public open val component: Component, public open val event: E @@ -124,7 +131,7 @@ public abstract class ComponentContext( /** * Given a translation key and possible replacements,return the translation for the given locale in the - * extension's configured bundle, for the locale provided by the bot's configured locale resolvers. + * component's configured bundle, for the locale provided by the bot's configured locale resolvers. */ public suspend fun translate(key: String, replacements: Array = arrayOf()): String = translate( key, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt index 4ea24d4f76..52794f320f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt @@ -7,29 +7,37 @@ import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent import mu.KLogger import mu.KotlinLogging +/** + * Component registry, keeps track of components and handles incoming interaction events, dispatching as needed to + * registered component actions. + */ public open class ComponentRegistry { internal val logger: KLogger = KotlinLogging.logger {} + /** Map of registered component IDs to their components. **/ public open val components: MutableMap = mutableMapOf() + /** Register a component. Only components with IDs need registering. **/ public open fun register(component: ComponentWithID) { logger.debug { "Registering component with ID: ${component.id}" } components[component.id] = component } + /** Unregister a registered component. **/ public open fun unregister(component: ComponentWithID): Component? = unregister(component.id) - public open fun unregister(id: String): Component? { - return components.remove(id) - } + /** Unregister a registered component, by ID. **/ + public open fun unregister(id: String): Component? = + components.remove(id) + /** Dispatch a [ButtonInteractionCreateEvent] to its button component object. **/ public suspend fun handle(event: ButtonInteractionCreateEvent) { val id = event.interaction.componentId when (val c = components[id]) { - is InteractionButtonWithAction<*> -> { c.call(event) } + is InteractionButtonWithAction<*> -> c.call(event) null -> logger.warn { "Button interaction received for unknown component ID: $id" } @@ -40,11 +48,12 @@ public open class ComponentRegistry { } } + /** Dispatch a [SelectMenuInteractionCreateEvent] to its select (dropdown) menu component object. **/ public suspend fun handle(event: SelectMenuInteractionCreateEvent) { val id = event.interaction.componentId when (val c = components[id]) { - is SelectMenu<*> -> { c.call(event) } + is SelectMenu<*> -> c.call(event) null -> logger.warn { "Select Menu interaction received for unknown component ID: $id" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index 8f68a954cd..07efa47693 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -12,16 +12,23 @@ import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import mu.KLogger import mu.KotlinLogging -public abstract class ComponentWithAction> - : ComponentWithID() { +/** + * Abstract class representing a component with both an ID and executable action. + * + * @param E Event type that triggers interaction actions for this component type + * @param C Context type used for this component's execution context + */ +public abstract class ComponentWithAction> : + ComponentWithID() { private val logger: KLogger = KotlinLogging.logger {} + /** Whether to use a deferred ack, which will prevent Discord's "Thinking..." message. **/ public open var deferredAck: Boolean = true /** @suppress **/ public open val checkList: MutableList> = mutableListOf() - /** Permissions required to be able to run execute this component's action. **/ + /** Bot permissions required to be able to run execute this component's action. **/ public open val requiredPerms: MutableSet = mutableSetOf() /** Component body, to be called when the component is interacted with. **/ @@ -94,8 +101,6 @@ public abstract class ComponentWithAction Unit @@ -21,6 +22,7 @@ public suspend fun ComponentContainer.disabledButton( return component } +/** DSL function for creating an ephemeral button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralButton( row: Int? = null, builder: suspend EphemeralInteractionButton.() -> Unit @@ -33,6 +35,7 @@ public suspend fun ComponentContainer.ephemeralButton( return component } +/** DSL function for creating a link button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.linkButton( row: Int? = null, builder: suspend LinkInteractionButton.() -> Unit @@ -45,6 +48,7 @@ public suspend fun ComponentContainer.linkButton( return component } +/** DSL function for creating a public button and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicButton( row: Int? = null, builder: suspend PublicInteractionButton.() -> Unit @@ -57,6 +61,7 @@ public suspend fun ComponentContainer.publicButton( return component } +/** DSL function for creating an ephemeral select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.ephemeralSelectMenu( row: Int? = null, builder: suspend EphemeralSelectMenu.() -> Unit @@ -69,6 +74,7 @@ public suspend fun ComponentContainer.ephemeralSelectMenu( return component } +/** DSL function for creating a public select menu and adding it to the current [ComponentContainer]. **/ public suspend fun ComponentContainer.publicSelectMenu( row: Int? = null, builder: suspend PublicSelectMenu.() -> Unit @@ -81,18 +87,24 @@ public suspend fun ComponentContainer.publicSelectMenu( return component } +/** Convenience function for applying the components in a [ComponentContainer] to a message you're creating. **/ public fun MessageCreateBuilder.applyComponents(components: ComponentContainer) { with(components) { applyToMessage() } } +/** Convenience function for applying the components in a [ComponentContainer] to a message you're editing. **/ public fun MessageModifyBuilder.applyComponents(components: ComponentContainer) { with(components) { applyToMessage() } } +/** + * Convenience function for creating a [ComponentContainer] and components, and applying it to a message you're + * creating. + */ public suspend fun MessageCreateBuilder.components(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { val container = ComponentContainer(builder) @@ -101,6 +113,10 @@ public suspend fun MessageCreateBuilder.components(builder: suspend ComponentCon return container } +/** + * Convenience function for creating a [ComponentContainer] and components, and applying it to a message you're + * editing. + */ public suspend fun MessageModifyBuilder.components(builder: suspend ComponentContainer.() -> Unit): ComponentContainer { val container = ComponentContainer(builder) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt index a06266d019..7c30ecdafd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/DisabledInteractionButton.kt @@ -3,7 +3,9 @@ package com.kotlindiscord.kord.extensions.components.buttons import dev.kord.common.entity.ButtonStyle import dev.kord.rest.builder.component.ActionRowBuilder +/** Class representing a disabled button component, which has no action. **/ public open class DisabledInteractionButton : InteractionButtonWithID() { + /** Button style - anything but Link is valid. **/ public open var style: ButtonStyle = ButtonStyle.Primary override fun apply(builder: ActionRowBuilder) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index f5323a56cb..891a630016 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.CommandException @@ -11,10 +13,19 @@ import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBu public typealias InitialEphemeralButtonResponseBuilder = (suspend EphemeralInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? +/** Class representing an ephemeral-only interaction button. **/ public open class EphemeralInteractionButton : InteractionButtonWithAction() { + /** Button style - anything but Link is valid. **/ public open var style: ButtonStyle = ButtonStyle.Primary + + /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialEphemeralButtonResponseBuilder = null + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralButtonResponseBuilder) { + initialResponseBuilder = body + } + override fun apply(builder: ActionRowBuilder) { builder.interactionButton(style, id) { emoji = partialEmoji diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt index 8f537398f8..e0215857f4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButtonContext.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContex import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +/** Class representing the execution context for an ephemeral-only button. **/ public class EphemeralInteractionButtonContext( override val component: EphemeralInteractionButton, override val event: ButtonInteractionCreateEvent, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt index 8351fd2c37..2eab4e78b6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButton.kt @@ -4,8 +4,11 @@ import com.kotlindiscord.kord.extensions.components.Component import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji import dev.kord.common.entity.DiscordPartialEmoji +/** Abstract class representing a button component. **/ public abstract class InteractionButton : Component(), HasPartialEmoji { + /** Button label, for display on Discord. **/ public var label: String? = null + public override var partialEmoji: DiscordPartialEmoji? = null override fun validate() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt index f272b87af2..facf324749 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonContext.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.components.Component import com.kotlindiscord.kord.extensions.components.ComponentContext import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +/** Abstract class representing the execution context for a button component's action. **/ public abstract class InteractionButtonContext( component: Component, event: ButtonInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt index cfcec9a5b6..2184393acd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt @@ -13,11 +13,14 @@ import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging -public abstract class InteractionButtonWithAction - : ComponentWithAction(), HasPartialEmoji { +/** Abstract class representing a button component that has a click action. **/ +public abstract class InteractionButtonWithAction : + ComponentWithAction(), HasPartialEmoji { internal val logger: KLogger = KotlinLogging.logger {} + /** Button label, for display on Discord. **/ public var label: String? = null + public override var partialEmoji: DiscordPartialEmoji? = null /** If enabled, adds the initial Sentry breadcrumb to the given context. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt index 0bae06dc8d..07ab7726a3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithID.kt @@ -4,7 +4,9 @@ import com.kotlindiscord.kord.extensions.components.ComponentWithID import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji import dev.kord.common.entity.DiscordPartialEmoji +/** Abstract class representing a button component with an ID, but without a click action. **/ public abstract class InteractionButtonWithID : ComponentWithID(), HasPartialEmoji { + /** Button label, for display on Discord. **/ public var label: String? = null public override var partialEmoji: DiscordPartialEmoji? = null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt index 6f228d1580..25b0659707 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/LinkInteractionButton.kt @@ -2,7 +2,9 @@ package com.kotlindiscord.kord.extensions.components.buttons import dev.kord.rest.builder.component.ActionRowBuilder +/** Class representing a linked button component, which opens a URL when clicked. **/ public open class LinkInteractionButton : InteractionButton() { + /** URL to send the user to when clicked. **/ public open lateinit var url: String override fun validate() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index e7fe060942..df5ca6373d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.CommandException @@ -11,10 +13,19 @@ import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuild public typealias InitialPublicButtonResponseBuilder = (suspend PublicInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? +/** Class representing a public-only button component. **/ public open class PublicInteractionButton : InteractionButtonWithAction() { + /** Button style - anything but Link is valid. **/ public open var style: ButtonStyle = ButtonStyle.Primary + + /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialPublicButtonResponseBuilder = null + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicButtonResponseBuilder) { + initialResponseBuilder = body + } + override fun apply(builder: ActionRowBuilder) { builder.interactionButton(style, id) { emoji = partialEmoji diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt index ff37f53466..39c8fe7cb5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButtonContext.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +/** Class representing the execution context for a public-only button. **/ public class PublicInteractionButtonContext( component: PublicInteractionButton, event: ButtonInteractionCreateEvent, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index effa0f95a3..0bd8ff9768 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.CommandException @@ -9,9 +11,16 @@ import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBu public typealias InitialEphemeralSelectMenuResponseBuilder = (suspend EphemeralInteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? +/** Class representing an ephemeral-only select (dropdown) menu. **/ public open class EphemeralSelectMenu : SelectMenu() { + /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialEphemeralSelectMenuResponseBuilder = null + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialEphemeralSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: SelectMenuInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt index 86a1d76d74..f7cdf04f19 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenuContext.kt @@ -5,6 +5,7 @@ import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContex import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +/** Class representing the execution context for an ephemeral-only select (dropdown) menu. **/ public class EphemeralSelectMenuContext( override val component: Component, override val event: SelectMenuInteractionCreateEvent, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt index 587b9021dd..81459ed861 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooGenericExceptionCaught") + package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.CommandException @@ -9,9 +11,16 @@ import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuild public typealias InitialPublicSelectMenuResponseBuilder = (suspend PublicInteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? +/** Class representing a public-only select (dropdown) menu. **/ public open class PublicSelectMenu : SelectMenu() { + /** @suppress Initial response builder. **/ public open var initialResponseBuilder: InitialPublicSelectMenuResponseBuilder = null + /** Call this to open with a response, omit it to ack instead. **/ + public fun initialResponse(body: InitialPublicSelectMenuResponseBuilder) { + initialResponseBuilder = body + } + override suspend fun call(event: SelectMenuInteractionCreateEvent) { try { if (!runChecks(event)) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt index 3b5fd2b8b0..d9f83e123c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenuContext.kt @@ -5,6 +5,7 @@ import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +/** Class representing the execution context for a public-only select (dropdown) menu. **/ public class PublicSelectMenuContext( override val component: Component, override val event: SelectMenuInteractionCreateEvent, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index 100e2e5e3e..f0d1d774ad 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -28,6 +28,7 @@ public const val PLACEHOLDER_MAX: Int = 100 /** Maximum length for an option's value. **/ public const val VALUE_MAX: Int = 100 +/** Abstract class representing a select (dropdown) menu component. **/ public abstract class SelectMenu : ComponentWithAction() { internal val logger: KLogger = KotlinLogging.logger {} @@ -43,15 +44,17 @@ public abstract class SelectMenu : ComponentWithAction Unit = {}) { val builder = SelectOptionBuilder(label, value) body(builder) - if (builder.description?.length ?: 0 > DESCRIPTION_MAX) { + if ((builder.description?.length ?: 0) > DESCRIPTION_MAX) { error("Option descriptions must not be longer than $DESCRIPTION_MAX characters.") } @@ -79,6 +82,7 @@ public abstract class SelectMenu : ComponentWithAction : ComponentWithAction PLACEHOLDER_MAX) { + if ((this.placeholder?.length ?: 0) > PLACEHOLDER_MAX) { error("Menu components must not have a placeholder longer than $PLACEHOLDER_MAX characters.") } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt index 00a7da7a22..62d0633d79 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenuContext.kt @@ -4,9 +4,11 @@ import com.kotlindiscord.kord.extensions.components.Component import com.kotlindiscord.kord.extensions.components.ComponentContext import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent +/** Abstract class representing the execution context of a select (dropdown) menu component. **/ public abstract class SelectMenuContext( component: Component, event: SelectMenuInteractionCreateEvent ) : ComponentContext(component, event) { - public val selected: List = event.interaction.values + /** Menu options that were selected by the user before de-focusing the menu. **/ + public val selected: List by lazy { event.interaction.values } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt index 2ff9f6d081..a87829c833 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/types/HasPartialEmoji.kt @@ -5,6 +5,10 @@ import dev.kord.common.entity.optional.optional import dev.kord.core.entity.GuildEmoji import dev.kord.core.entity.ReactionEmoji +/** + * Interface representing a button type that has a partial emoji property. This is used to keep the [emoji] + * function DRY. + */ public interface HasPartialEmoji { /** * A partial emoji object, either a guild or Unicode emoji. Optional if you've got a label. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt index 3597133b01..a761f2ee7b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt @@ -10,7 +10,7 @@ import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilde import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder import java.util.* -/** Interface representing an ephemeral-only application command context. **/ +/** Interface representing an ephemeral-only interaction action context. **/ public interface EphemeralInteractionContext { /** Response created by acknowledging the interaction ephemerally. **/ public val interactionResponse: EphemeralInteractionResponseBehavior @@ -32,6 +32,10 @@ public suspend inline fun EphemeralInteractionContext.edit( builder: EphemeralInteractionResponseModifyBuilder.() -> Unit ): Unit = interactionResponse.edit(builder) +/** + * Create a paginator that edits the original interaction. This is the only option for an ephemeral interaction, as + * it's impossible to edit an ephemeral follow-up. + */ public suspend inline fun EphemeralInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt index 342860fe3e..784384fe69 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt @@ -12,7 +12,7 @@ import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder import java.util.* -/** Interface representing a public-only application command context. **/ +/** Interface representing a public-only interaction action context. **/ public interface PublicInteractionContext { /** Response created by acknowledging the interaction publicly. **/ public val interactionResponse: PublicInteractionResponseBehavior @@ -30,6 +30,7 @@ public suspend inline fun PublicInteractionContext.edit( builder: PublicInteractionResponseModifyBuilder.() -> Unit ): Message = interactionResponse.edit(builder) +/** Create a paginator that edits the original interaction. **/ public suspend inline fun PublicInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, @@ -42,6 +43,7 @@ public suspend inline fun PublicInteractionContext.editingPaginator( return PublicResponsePaginator(pages, interactionResponse) } +/** Create a paginator that creates a follow-up message, and edits that. **/ public suspend inline fun PublicInteractionContext.respondingPaginator( defaultGroup: String = "", locale: Locale? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt index 95c85bd063..30a9471e4b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt @@ -27,6 +27,7 @@ public class EphemeralResponsePaginator( public val interaction: EphemeralInteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, true, switchEmoji, bundle, locale) { + /** Whether this paginator has been set up for the first time. **/ public var isSetup: Boolean = false override suspend fun send() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt index 5618242b5d..c02789c353 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt @@ -15,7 +15,6 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed import java.util.* - /** * Class representing a button-based paginator that operates by creating and editing a follow-up message for the * given public interaction response. @@ -33,6 +32,7 @@ public class PublicFollowUpPaginator( public val interaction: PublicInteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { + /** Follow-up interaction to use for this paginator's embeds. Will be created by [send]. **/ public var embedInteraction: PublicFollowupMessage? = null override suspend fun send() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt index 08af2f7e3e..78ba176bb0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt @@ -28,6 +28,7 @@ public class PublicResponsePaginator( public val interaction: PublicInteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { + /** Whether this paginator has been set up for the first time. **/ public var isSetup: Boolean = false override suspend fun send() { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt index 56b5534620..775d286f0b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt @@ -5,6 +5,7 @@ import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import java.util.* +/** Create a paginator that edits the original interaction. **/ public suspend inline fun PublicInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", @@ -17,6 +18,7 @@ public suspend inline fun PublicInteractionResponseBehavior.editingPaginator( return PublicResponsePaginator(pages, this) } +/** Create a paginator that creates a follow-up message, and edits that. **/ public suspend inline fun PublicInteractionResponseBehavior.respondingPaginator( locale: Locale? = null, defaultGroup: String = "", @@ -29,6 +31,10 @@ public suspend inline fun PublicInteractionResponseBehavior.respondingPaginator( return PublicFollowUpPaginator(pages, this) } +/** + * Create a paginator that edits the original interaction. This is the only option for an ephemeral interaction, as + * it's impossible to edit an ephemeral follow-up. + */ public suspend inline fun EphemeralInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 8b9fa2748f..0dffafe29d 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -209,7 +209,6 @@ class TestExtension : Extension() { } } - page("Expanded") { description = "Expanded page $it, expanded page $it\n" + "Expanded page $it, expanded page $it" From 2310387366e0e28f91fd469d9e98dc5cde2a246f Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 6 Sep 2021 22:17:38 +0100 Subject: [PATCH 025/131] Remove old deprecations --- .../kord/extensions/ExtensibleBot.kt | 70 ------------------- .../builders/ExtensibleBotBuilder.kt | 8 --- 2 files changed, 78 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index c95478da77..757e4eab3e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -3,9 +3,7 @@ package com.kotlindiscord.kord.extensions import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommandRegistry -import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.components.ComponentRegistry import com.kotlindiscord.kord.extensions.events.EventHandler @@ -34,7 +32,6 @@ import kotlinx.coroutines.launch import mu.KLogger import mu.KotlinLogging import org.koin.core.component.KoinComponent -import org.koin.core.component.inject import org.koin.dsl.bind /** @@ -50,22 +47,6 @@ import org.koin.dsl.bind * @param token Token for connecting to Discord. */ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, private val token: String) : KoinComponent { - /** - * @suppress - */ - @Deprecated( - "Use Koin to get this instead. This will be private in future.", - - ReplaceWith( - "getKoin().get()", - - "com.kotlindiscord.kord.extensions.utils.getKoin", - "dev.kord.core.Kord" - ), - level = DeprecationLevel.ERROR - ) - public val kord: Kord by inject() - /** * A list of all registered event handlers. */ @@ -324,57 +305,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } } - /** - * Directly register a [ChatCommand] to this bot. - * - * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and - * call the [Extension.messageContentCommand] function in your [Extension.setup] function. - * - * This function will throw a [CommandRegistrationException] if the command has already been registered, if - * a command with the same name exists, or if a command with one of the same aliases exists. - * - * @param command The command to be registered. - * @throws CommandRegistrationException Thrown if the command could not be registered. - */ - @Deprecated( - "Use the equivalent function within `MessageContentCommandRegistry` instead.", - - ReplaceWith( - "getKoin().get().add(command)", - - "org.koin.core.component.KoinComponent.getKoin", - "com.kotlindiscord.kord.extensions.commands.MessageContentCommand" - ), - level = DeprecationLevel.ERROR - ) - @Throws(CommandRegistrationException::class) - public open fun addCommand(command: ChatCommand): Unit = getKoin() - .get() - .add(command) - - /** - * Directly remove a registered [ChatCommand] from this bot. - * - * This function is used when extensions are unloaded, in order to clear out their commands. - * No exception is thrown if the command wasn't registered. - * - * @param command The command to be removed. - */ - @Deprecated( - "Use the equivalent function within `MessageContentCommandRegistry` instead.", - - ReplaceWith( - "getKoin().get().remove(command)", - - "org.koin.core.component.KoinComponent.getKoin", - "com.kotlindiscord.kord.extensions.commands.MessageContentCommand" - ), - level = DeprecationLevel.ERROR - ) - public open fun removeCommand(command: ChatCommand): Boolean = getKoin() - .get() - .remove(command) - /** * Directly register an [EventHandler] to this bot. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index c0a2ce293d..83edf70e5b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -355,14 +355,6 @@ public open class ExtensibleBotBuilder { /** @suppress Sentry extension builder. **/ public open val sentryExtensionBuilder: SentryExtensionBuilder = SentryExtensionBuilder() - /** Whether to enable the bundled Sentry extension. Defaults to `true`. **/ - @Deprecated("Use the sentry { } builder instead.", level = DeprecationLevel.ERROR) - public var sentry: Boolean - get() = sentryExtensionBuilder.debug - set(value) { - sentryExtensionBuilder.debug = value - } - /** Add a custom extension to the bot via a builder - probably the extension constructor. **/ public open fun add(builder: () -> Extension) { extensions.add(builder) From f5656bf88479edd49edf1ea78d869b2904b2b848 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 7 Sep 2021 11:02:41 +0100 Subject: [PATCH 026/131] Most debug log calls are now trace calls --- .../modules/extra/mappings/Checks.kt | 30 +++++++++---------- .../application/ApplicationCommandRegistry.kt | 10 +++---- .../application/message/MessageCommand.kt | 4 +-- .../application/slash/SlashCommand.kt | 4 +-- .../application/slash/SlashCommandParser.kt | 18 +++++------ .../commands/application/user/UserCommand.kt | 4 +-- .../extensions/commands/chat/ChatCommand.kt | 4 +-- .../commands/chat/ChatCommandParser.kt | 24 +++++++-------- .../converters/impl/MessageConverter.kt | 10 +++---- .../components/ComponentRegistry.kt | 2 +- .../buttons/InteractionButtonWithAction.kt | 4 +-- .../extensions/components/menus/SelectMenu.kt | 4 +-- .../kord/extensions/events/EventHandler.kt | 4 +-- .../kord/extensions/extensions/Extension.kt | 7 +++-- .../kord/extensions/utils/_Environment.kt | 2 +- .../src/test/resources/logback.groovy | 2 +- .../modules/time/java/ChronoContainer.kt | 2 +- 17 files changed, 69 insertions(+), 66 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt index 1dbcadfec4..9444daf8d1 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt @@ -34,11 +34,11 @@ fun allowedCategory( val channel = channelFor(event) if (channel == null) { - logger.debug { "Passing: Event is not channel-related" } + logger.trace { "Passing: Event is not channel-related" } pass() } else if (channel !is CategorizableChannel) { - logger.debug { "Passing: Channel is not categorizable (eg, it's a DM)" } + logger.trace { "Passing: Channel is not categorizable (eg, it's a DM)" } pass() } else { @@ -50,7 +50,7 @@ fun allowedCategory( fail() } else if (allowed.contains(parent.id)) { - logger.debug { "Passing: Event happened in an allowed category" } + logger.trace { "Passing: Event happened in an allowed category" } pass() } else { @@ -60,14 +60,14 @@ fun allowedCategory( } } else { if (parent == null) { - logger.debug { + logger.trace { "Passing: We have no allowed categories, and the message was sent outside of a category" } pass() } else if (banned.isNotEmpty()) { if (!banned.contains(parent.id)) { - logger.debug { "Passing: Event did not happen in a banned category" } + logger.trace { "Passing: Event did not happen in a banned category" } pass() } else { @@ -76,7 +76,7 @@ fun allowedCategory( fail() } } else { - logger.debug { "Passing: No allowed or banned categories configured" } + logger.trace { "Passing: No allowed or banned categories configured" } pass() } @@ -108,16 +108,16 @@ fun allowedChannel( val channel = channelFor(event) if (channel == null) { - logger.debug { "Passing: Event is not channel-related" } + logger.trace { "Passing: Event is not channel-related" } pass() } else if (channel !is GuildChannel) { - logger.debug { "Passing: Message was sent privately" } + logger.trace { "Passing: Message was sent privately" } pass() // It's a DM } else if (allowed.isNotEmpty()) { if (allowed.contains(channel.id)) { - logger.debug { "Passing: Event happened in an allowed channel" } + logger.trace { "Passing: Event happened in an allowed channel" } pass() } else { @@ -127,7 +127,7 @@ fun allowedChannel( } } else if (banned.isNotEmpty()) { if (!banned.contains(channel.id)) { - logger.debug { "Passing: Event did not happen in a banned channel" } + logger.trace { "Passing: Event did not happen in a banned channel" } pass() } else { @@ -136,7 +136,7 @@ fun allowedChannel( fail() } } else { - logger.debug { "Passing: No allowed or banned channels configured" } + logger.trace { "Passing: No allowed or banned channels configured" } pass() } @@ -165,12 +165,12 @@ fun allowedGuild( val guild = guildFor(event) if (guild == null) { - logger.debug { "Passing: Event is not guild-related" } + logger.trace { "Passing: Event is not guild-related" } pass() } else if (allowed.isNotEmpty()) { if (allowed.contains(guild.id)) { - logger.debug { "Passing: Event happened in an allowed guild" } + logger.trace { "Passing: Event happened in an allowed guild" } pass() } else { @@ -180,7 +180,7 @@ fun allowedGuild( } } else if (banned.isNotEmpty()) { if (!banned.contains(guild.id)) { - logger.debug { "Passing: Event did not happen in a banned guild" } + logger.trace { "Passing: Event did not happen in a banned guild" } pass() } else { @@ -189,7 +189,7 @@ fun allowedGuild( fail() } } else { - logger.debug { "Passing: No allowed or banned guilds configured" } + logger.trace { "Passing: No allowed or banned guilds configured" } pass() } 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 d0cc9d4c62..8131a57713 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 @@ -226,7 +226,7 @@ val response = if (guild == null) { toCreate.forEach { val name = it.getTranslatedName(locale) - logger.debug { "Adding/updating global ${it.type.name} command: $name" } + logger.trace { "Adding/updating global ${it.type.name} command: $name" } when (it) { is MessageCommand<*> -> message(name) { this.register(locale, it) } @@ -245,7 +245,7 @@ val response = if (guild == null) { toCreate.forEach { val name = it.getTranslatedName(locale) - logger.debug { "Adding/updating guild-specific ${it.type.name} command: $name" } + logger.trace { "Adding/updating guild-specific ${it.type.name} command: $name" } when (it) { is MessageCommand<*> -> message(name) { this.register(locale, it) } @@ -272,7 +272,7 @@ val response = if (guild == null) { // Finally, we can remove anything that needs to be removed toRemove.forEach { - logger.debug { "Removing ${it.type.name} command: ${it.name}" } + logger.trace { "Removing ${it.type.name} command: ${it.name}" } it.delete() } @@ -301,7 +301,7 @@ val response = if (guild == null) { kord.createGlobalApplicationCommands { val name = command.getTranslatedName(locale) - logger.debug { "Adding/updating global ${command.type.name} command: $name" } + logger.trace { "Adding/updating global ${command.type.name} command: $name" } when (command) { is MessageCommand<*> -> message(name) { this.register(locale, command) } @@ -324,7 +324,7 @@ val response = if (guild == null) { guild.createApplicationCommands { val name = command.getTranslatedName(locale) - logger.debug { "Adding/updating guild-specific ${command.type.name} command: $name" } + logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } when (command) { is MessageCommand<*> -> message(name) { this.register(locale, command) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 72c0a41179..b34096182b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -144,7 +144,7 @@ public abstract class MessageCommand>( logger.error(t) { "Error during execution of $name message command (${context.event})" } if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val channel = context.channel val author = context.user.asUserOrNull() @@ -166,7 +166,7 @@ public abstract class MessageCommand>( Sentry.captureException(t, "Message command execution failed.") } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 3d2e38329c..0e9a084ae9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -201,7 +201,7 @@ public abstract class SlashCommand, A : Arguments> logger.error(t) { "Error during execution of ${commandObj.name} slash command (${context.event})" } if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val channel = context.channel val author = context.user.asUserOrNull() @@ -223,7 +223,7 @@ public abstract class SlashCommand, A : Arguments> Sentry.captureException(t, "Slash command execution failed.") } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt index 765fbdd008..e313a16b95 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt @@ -37,7 +37,7 @@ public open class SlashCommandParser { ): T { val argumentsObj = builder.invoke() - logger.debug { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } + logger.trace { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } val args = argumentsObj.args.toMutableList() val command = context.event.interaction.command @@ -60,11 +60,11 @@ public open class SlashCommandParser { currentArg = args.removeFirstOrNull() currentArg ?: break // If it's null, we're out of arguments - logger.debug { "Current argument: ${currentArg.displayName}" } + logger.trace { "Current argument: ${currentArg.displayName}" } currentValue = values[currentArg.displayName.lowercase()] - logger.debug { "Current value: $currentValue" } + logger.trace { "Current value: $currentValue" } @Suppress("TooGenericExceptionCaught") when (val converter = currentArg.converter) { @@ -92,7 +92,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null @@ -132,7 +132,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null @@ -159,7 +159,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null @@ -184,7 +184,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null @@ -209,7 +209,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null @@ -234,7 +234,7 @@ public open class SlashCommandParser { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true currentValue = null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index f0499a5d27..63a39aeaeb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -144,7 +144,7 @@ public abstract class UserCommand>( logger.error(t) { "Error during execution of $name user command (${context.event})" } if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val channel = context.channel val author = context.user.asUserOrNull() @@ -166,7 +166,7 @@ public abstract class UserCommand>( Sentry.captureException(t, "User command execution failed.") } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } val errorMessage = if (extension.bot.extensions.containsKey("sentry")) { context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index dd0ab76356..0ce6ad2abf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -448,7 +448,7 @@ public open class ChatCommand( event.message.respond(e.toString()) } catch (t: Throwable) { if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val channel = event.message.getChannelOrNull() @@ -476,7 +476,7 @@ public open class ChatCommand( tag("extension", extension.name) } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } sentry.addEventId(sentryId) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt index 58652f1721..b744cd7e3f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt @@ -69,7 +69,7 @@ public open class ChatCommandParser : KoinComponent { val argumentsObj = builder.invoke() val parser = context.parser!! - logger.debug { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } + logger.trace { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } val args = argumentsObj.args.toMutableList() val argsMap = args.map { Pair(it.displayName.lowercase(), it) }.toMap() @@ -82,7 +82,7 @@ public open class ChatCommandParser : KoinComponent { keywordArgs[name]!!.add(it.data) } - logger.debug { "Args map: $argsMap" } + logger.trace { "Args map: $argsMap" } var currentArg: Argument<*>? @@ -94,8 +94,8 @@ public open class ChatCommandParser : KoinComponent { val kwValue = keywordArgs[currentArg.displayName.lowercase()] val hasKwargs = kwValue != null - logger.debug { "Current argument: ${currentArg.displayName}" } - logger.debug { "Keyword arg ($hasKwargs): $kwValue" } + logger.trace { "Current argument: ${currentArg.displayName}" } + logger.trace { "Keyword arg ($hasKwargs): $kwValue" } if (!parser.cursor.hasNext && !hasKwargs) { continue @@ -132,7 +132,7 @@ public open class ChatCommandParser : KoinComponent { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true @@ -177,7 +177,7 @@ public open class ChatCommandParser : KoinComponent { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true @@ -218,7 +218,7 @@ public open class ChatCommandParser : KoinComponent { } if (parsed) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true @@ -285,7 +285,7 @@ public open class ChatCommandParser : KoinComponent { converter.parseSuccess = true } else { if (parsedCount > 0) { - logger.debug { "Argument ${currentArg.displayName} successfully filled." } + logger.trace { "Argument ${currentArg.displayName} successfully filled." } converter.parseSuccess = true @@ -353,7 +353,7 @@ public open class ChatCommandParser : KoinComponent { converter.parseSuccess = true } else { if (parsedCount > 0) { - logger.debug { "Argument '${currentArg.displayName}' successfully filled." } + logger.trace { "Argument '${currentArg.displayName}' successfully filled." } converter.parseSuccess = true @@ -421,7 +421,7 @@ public open class ChatCommandParser : KoinComponent { converter.parseSuccess = true } else { if (parsedCount > 0) { - logger.debug { "Argument '${currentArg.displayName}' successfully filled." } + logger.trace { "Argument '${currentArg.displayName}' successfully filled." } converter.parseSuccess = true @@ -489,7 +489,7 @@ public open class ChatCommandParser : KoinComponent { converter.parseSuccess = true } else { if (parsedCount > 0) { - logger.debug { "Argument '${currentArg.displayName}' successfully filled." } + logger.trace { "Argument '${currentArg.displayName}' successfully filled." } converter.parseSuccess = true @@ -538,7 +538,7 @@ public open class ChatCommandParser : KoinComponent { val allRequiredArgs = argsMap.count { it.value.converter.required } val filledRequiredArgs = argsMap.count { it.value.converter.parseSuccess && it.value.converter.required } - logger.debug { "Filled $filledRequiredArgs / $allRequiredArgs arguments." } + logger.trace { "Filled $filledRequiredArgs / $allRequiredArgs arguments." } if (filledRequiredArgs < allRequiredArgs) { if (filledRequiredArgs < 1) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt index c50f339373..43e3428d17 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt @@ -111,7 +111,7 @@ public class MessageConverter( } if (requireGuild && requiredGid != gid) { - logger.debug { "Matching guild ($requiredGid) required, but guild ($gid) doesn't match." } + logger.trace { "Matching guild ($requiredGid) required, but guild ($gid) doesn't match." } errorNoMessage(arg, context) } @@ -131,13 +131,13 @@ public class MessageConverter( val channel: GuildChannel? = kord.getGuild(gid)?.getChannel(cid) if (channel == null) { - logger.debug { "Unable to find channel ($cid) for guild ($gid)." } + logger.trace { "Unable to find channel ($cid) for guild ($gid)." } errorNoMessage(arg, context) } if (channel !is GuildMessageChannel) { - logger.debug { "Specified channel ($cid) is not a guild message channel." } + logger.trace { "Specified channel ($cid) is not a guild message channel." } errorNoMessage(arg, context) } @@ -163,13 +163,13 @@ public class MessageConverter( val channel: ChannelBehavior? = context.getChannel() if (channel !is GuildMessageChannel && channel !is DmChannel) { - logger.debug { "Current channel is not a guild message channel or DM channel." } + logger.trace { "Current channel is not a guild message channel or DM channel." } errorNoMessage(arg, context) } if (channel !is MessageChannel) { - logger.debug { "Current channel is not a message channel, so it can't contain messages." } + logger.trace { "Current channel is not a message channel, so it can't contain messages." } errorNoMessage(arg, context) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt index 52794f320f..1c3f67a0c9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt @@ -19,7 +19,7 @@ public open class ComponentRegistry { /** Register a component. Only components with IDs need registering. **/ public open fun register(component: ComponentWithID) { - logger.debug { "Registering component with ID: ${component.id}" } + logger.trace { "Registering component with ID: ${component.id}" } components[component.id] = component } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt index 2184393acd..c6007fb8d2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt @@ -61,7 +61,7 @@ public abstract class InteractionButtonWithAction logger.error(t) { "Error during execution of button (${button.id}) action (${context.event})" } if (sentry.enabled) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val channel = context.channel val author = context.user.asUserOrNull() @@ -82,7 +82,7 @@ public abstract class InteractionButtonWithAction Sentry.captureException(t, "Slash command execution failed.") } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } val errorMessage = if (bot.extensions.containsKey("sentry")) { context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index f0d1d774ad..826145c913 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -137,7 +137,7 @@ public abstract class SelectMenu : ComponentWithAction : ComponentWithAction( this.body(context) } catch (t: Throwable) { if (sentry.enabled && extension.bot.extensions.containsKey("sentry")) { - logger.debug { "Submitting error to sentry." } + logger.trace { "Submitting error to sentry." } val sentryId = context.sentry.captureException(t, "Event processing failed.") { tag("event", eventName ?: "Unknown") tag("extension", extension.name) } - logger.debug { "Error submitted to Sentry: $sentryId" } + logger.info { "Error submitted to Sentry: $sentryId" } logger.error(t) { "Error during execution of event handler ($eventName)" } } else { 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 b7e26f2c78..927fdfb7ca 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 @@ -3,7 +3,10 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.checks.types.* +import com.kotlindiscord.kord.extensions.checks.types.ChatCommandCheck +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.ApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand @@ -174,7 +177,7 @@ public abstract class Extension : KoinComponent { * handled for you. */ public open suspend fun unload() { - logger.debug { "Unload function not overridden." } + logger.trace { "Unload function not overridden." } } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt index c9765a7902..7a38f359ee 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt @@ -64,7 +64,7 @@ public fun env(name: String): String? { continue } - logger.debug { "${split[0]} -> ${split[1]}" } + logger.trace { "${split[0]} -> ${split[1]}" } envMap[split[0]] = split[1] } diff --git a/kord-extensions/src/test/resources/logback.groovy b/kord-extensions/src/test/resources/logback.groovy index 1bab8d7d2d..6a2d39fbfe 100644 --- a/kord-extensions/src/test/resources/logback.groovy +++ b/kord-extensions/src/test/resources/logback.groovy @@ -2,7 +2,7 @@ import ch.qos.logback.core.joran.spi.ConsoleTarget def environment = System.getenv().getOrDefault("ENVIRONMENT", "production") -def defaultLevel = DEBUG +def defaultLevel = TRACE if (environment == "spam") { logger("dev.kord.rest.DefaultGateway", TRACE) diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt index 398fe34919..7772ff6d8b 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/ChronoContainer.kt @@ -173,7 +173,7 @@ public class ChronoContainer { if (target.isSupported(unit)) { result = result.plus(value, unit) as T } else { - logger.debug { "Unit $unit is not supported by $target" } + logger.trace { "Unit $unit is not supported by $target" } } } From 8f3f535b87fdbd73990bb7f5740a66962bfb071e Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 7 Sep 2021 11:30:58 +0100 Subject: [PATCH 027/131] ComponentContainer clears components during editing --- .../kord/extensions/components/ComponentContainer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt index aa0c81ebf5..bb191b15e5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt @@ -202,6 +202,8 @@ public open class ComponentContainer : KoinComponent { /** Apply the components in this container to a message that's being edited. **/ public open fun MessageModifyBuilder.applyToMessage() { + this.components = mutableListOf() // Clear 'em + sort() for (row in rows.filter { it.isNotEmpty() }) { From c5cebe905886d21fb1b962a2ee87571f06fec994 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 10:29:16 +0100 Subject: [PATCH 028/131] Validation function for Arguments subtypes --- .../kord/extensions/commands/Arguments.kt | 15 +++++++++++++++ .../application/slash/SlashCommandParser.kt | 1 + .../extensions/commands/chat/ChatCommandParser.kt | 2 ++ 3 files changed, 18 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt index 9bca939d9c..e581502b5f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Arguments.kt @@ -159,4 +159,19 @@ public open class Arguments { return converter } + + /** Validation function that will throw an error if there's a problem with this Arguments class/subclass. **/ + public open fun validate() { + val names: MutableSet = mutableSetOf() + + args.forEach { + val name = it.displayName.lowercase() + + if (name in names) { + error("Duplicate argument name: ${it.displayName}") + } + + names.add(name) + } + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt index e313a16b95..2e01d1d316 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt @@ -36,6 +36,7 @@ public open class SlashCommandParser { context: SlashCommandContext<*, *> ): T { val argumentsObj = builder.invoke() + argumentsObj.validate() logger.trace { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt index b744cd7e3f..1178b5bc19 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt @@ -67,6 +67,8 @@ public open class ChatCommandParser : KoinComponent { */ public open suspend fun parse(builder: () -> T, context: ChatCommandContext<*>): T { val argumentsObj = builder.invoke() + argumentsObj.validate() + val parser = context.parser!! logger.trace { "Arguments object: $argumentsObj (${argumentsObj.args.size} args)" } From 9e0a0276a44c5c714a5f0b0c6d67827adfa750a1 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 10:39:47 +0100 Subject: [PATCH 029/131] Better application command registration errors --- .../application/ApplicationCommandRegistry.kt | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) 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 8131a57713..ab270a657e 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 @@ -22,6 +22,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.* +import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.toList import mu.KotlinLogging import org.koin.core.component.KoinComponent @@ -100,6 +101,18 @@ public open class ApplicationCommandRegistry : KoinComponent { groupedCommands.forEach { try { sync(removeOthers, it.key, it.value) + } catch (e: KtorRequestException) { + logger.error(e) { + if (it.key == null) { + "Failed to synchronise global application commands" + } else { + "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" + } + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } } catch (t: Throwable) { logger.error(t) { if (it.key == null) { @@ -140,6 +153,16 @@ public open class ApplicationCommandRegistry : KoinComponent { logger.warn { "Applying permissions to global application commands is currently not supported." } } } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions - for this reason, all commands with configured" + + "permissions will be disabled." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } } catch (t: Throwable) { logger.error(t) { "Failed to apply application command permissions - for this reason, all commands with configured" + @@ -219,7 +242,7 @@ public open class ApplicationCommandRegistry : KoinComponent { val toCreate = toAdd + toUpdate @Suppress("IfThenToElvis") // Ultimately, this is far more readable -val response = if (guild == null) { + val response = if (guild == null) { // We're registering global commands here, if the guild is null kord.createGlobalApplicationCommands { @@ -357,6 +380,15 @@ val response = if (guild == null) { } else { logger.warn { "Applying permissions to global application commands is currently not supported." } } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions. This command will not be registered." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } } catch (t: Throwable) { logger.error(t) { "Failed to apply application command permissions. This command will not be registered." @@ -383,6 +415,17 @@ val response = if (guild == null) { commands.map { try { registerGeneric(it) as MessageCommand<*> + } catch (e: KtorRequestException) { + logger.warn(e) { + "Failed to register ${it.type.name} command: ${it.name}" + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + + null } catch (t: Throwable) { logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } @@ -395,6 +438,17 @@ val response = if (guild == null) { commands.map { try { registerGeneric(it) as SlashCommand<*, *> + } catch (e: KtorRequestException) { + logger.warn(e) { + "Failed to register ${it.type.name} command: ${it.name}" + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + + null } catch (t: Throwable) { logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } @@ -407,6 +461,17 @@ val response = if (guild == null) { commands.map { try { registerGeneric(it) as UserCommand<*> + } catch (e: KtorRequestException) { + logger.warn(e) { + "Failed to register ${it.type.name} command: ${it.name}" + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + + null } catch (t: Throwable) { logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } @@ -435,9 +500,14 @@ val response = if (guild == null) { // region: Unregistration functions /** Unregister a message command. **/ - public open suspend fun unregisterGeneric(command: ApplicationCommand<*>) { - TODO() - } + public open suspend fun unregisterGeneric(command: ApplicationCommand<*>): ApplicationCommand<*>? = + when (command) { + is MessageCommand<*> -> unregister(command) + is SlashCommand<*, *> -> unregister(command) + is UserCommand<*> -> unregister(command) + + else -> error("Unsupported application command type: ${command.type.name}") + } /** Unregister a message command. **/ public open suspend fun unregister(command: MessageCommand<*>): MessageCommand<*>? { From 2865d929e770f83d980cfd63cf5a1ebfbf889135 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 11:55:58 +0100 Subject: [PATCH 030/131] First version of DSL-style checks system --- .../ext/common/extensions/EmojiExtension.kt | 23 ++-- .../modules/extra/mappings/Checks.kt | 14 +-- .../extra/mappings/MappingsExtension.kt | 87 +++++++------ .../mappings/builders/ExtMappingsBuilder.kt | 8 +- .../kord/extensions/test/bot/Bot.kt | 4 +- .../kord/extensions/checks/ChannelChecks.kt | 114 ++++++++++++++---- .../extensions/checks/ChannelTypeChecks.kt | 21 ++-- .../extensions/checks/CheckCombinators.kt | 87 ------------- .../kord/extensions/checks/GuildChecks.kt | 42 +++++-- .../kord/extensions/checks/MemberChecks.kt | 20 +-- .../kord/extensions/checks/MiscChecks.kt | 31 +++-- .../kord/extensions/checks/RoleChecks.kt | 114 ++++++++++++++---- .../extensions/checks/TopChannelChecks.kt | 30 +++-- .../extensions/checks/types/CheckContext.kt | 40 +++++- .../extensions/commands/chat/ChatCommand.kt | 41 +------ .../kord/extensions/events/EventHandler.kt | 38 ------ .../kord/extensions/test/bot/Bot.kt | 4 +- 17 files changed, 392 insertions(+), 326 deletions(-) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt diff --git a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt index a8b697f4bb..b671c9b2fa 100644 --- a/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt +++ b/extra-modules/extra-common/src/main/kotlin/com/kotlindiscord/kordex/ext/common/extensions/EmojiExtension.kt @@ -1,7 +1,6 @@ package com.kotlindiscord.kordex.ext.common.extensions import com.kotlindiscord.kord.extensions.checks.inGuild -import com.kotlindiscord.kord.extensions.checks.or import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.event import com.kotlindiscord.kordex.ext.common.builders.ExtCommonBuilder @@ -25,19 +24,15 @@ class EmojiExtension : Extension() { } event { - check( - or( - // No configured guilds? Do them all. - { config.getGuilds().isEmpty() }, - - { - config.getGuilds() - .mapNotNull { kord.getGuild(it) } - .map { guild -> inGuild { guild } } - .any() - } - ) - ) + check { + failIfNot { + config.getGuilds().isEmpty() || + config.getGuilds() + .mapNotNull { kord.getGuild(it) } + .map { guild -> inGuild { guild } } + .any() + } + } action { populateEmojis(event.guildId) } } diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt index 9444daf8d1..1f3683470d 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/Checks.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings import com.kotlindiscord.kord.extensions.checks.channelFor import com.kotlindiscord.kord.extensions.checks.guildFor -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.entity.Snowflake import dev.kord.core.entity.channel.CategorizableChannel import dev.kord.core.entity.channel.GuildChannel @@ -26,10 +26,10 @@ import mu.KotlinLogging * @param allowed List of allowed category IDs * @param banned List of banned category IDs */ -fun allowedCategory( +suspend fun CheckContext.allowedCategory( allowed: List, banned: List -): Check = { +) { val logger = KotlinLogging.logger { } val channel = channelFor(event) @@ -100,10 +100,10 @@ fun allowedCategory( * @param allowed List of allowed channel IDs * @param banned List of banned channel IDs */ -fun allowedChannel( +suspend fun CheckContext.allowedChannel( allowed: List, banned: List -): Check = { +) { val logger = KotlinLogging.logger { } val channel = channelFor(event) @@ -157,10 +157,10 @@ fun allowedChannel( * @param allowed List of allowed guild IDs * @param banned List of banned guild IDs */ -fun allowedGuild( +suspend fun CheckContext.allowedGuild( allowed: List, banned: List -): Check = { +) { val logger = KotlinLogging.logger { } val guild = guildFor(event) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index 1468675454..f7fabab576 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings -import com.kotlindiscord.kord.extensions.checks.and import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext import com.kotlindiscord.kord.extensions.extensions.Extension @@ -22,6 +22,7 @@ import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.core.behavior.channel.withTyping +import dev.kord.core.event.message.MessageCreateEvent import me.shedaniel.linkie.* import me.shedaniel.linkie.namespaces.* import me.shedaniel.linkie.utils.MappingsQuery @@ -74,9 +75,17 @@ class MappingsExtension : Extension() { val patchworkEnabled = builder.config.yarnChannelEnabled(YarnChannels.PATCHWORK) - val categoryCheck = allowedCategory(builder.config.getAllowedCategories(), builder.config.getBannedCategories()) - val channelCheck = allowedGuild(builder.config.getAllowedChannels(), builder.config.getBannedChannels()) - val guildCheck = allowedGuild(builder.config.getAllowedGuilds(), builder.config.getBannedGuilds()) + val categoryCheck: Check = { + allowedCategory(builder.config.getAllowedCategories(), builder.config.getBannedCategories()) + } + + val channelCheck: Check = { + allowedGuild(builder.config.getAllowedChannels(), builder.config.getBannedChannels()) + } + + val guildCheck: Check = { + allowedGuild(builder.config.getAllowedGuilds(), builder.config.getBannedGuilds()) + } val yarnChannels = YarnChannels.values().filter { it != YarnChannels.PATCHWORK || patchworkEnabled @@ -95,7 +104,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Legacy Yarn mappings, you can use the " + "`lyarn` command." - check(customChecks(name, LegacyYarnNamespace)) + check { customChecks(name, LegacyYarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -113,7 +122,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Legacy Yarn mappings, you can use the " + "`lyarn` command." - check(customChecks(name, LegacyYarnNamespace)) + check { customChecks(name, LegacyYarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -131,7 +140,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Legacy Yarn mappings, you can use the " + "`lyarn` command." - check(customChecks(name, LegacyYarnNamespace)) + check { customChecks(name, LegacyYarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -153,7 +162,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for MCP mappings, you can use the `mcp` command." - check(customChecks(name, MCPNamespace)) + check { customChecks(name, MCPNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -169,7 +178,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for MCP mappings, you can use the `mcp` command." - check(customChecks(name, MCPNamespace)) + check { customChecks(name, MCPNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -185,7 +194,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for MCP mappings, you can use the `mcp` command." - check(customChecks(name, MCPNamespace)) + check { customChecks(name, MCPNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -212,7 +221,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + "command." - check(customChecks(name, MojangNamespace)) + check { customChecks(name, MojangNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -233,7 +242,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + "command." - check(customChecks(name, MojangNamespace)) + check { customChecks(name, MojangNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -254,7 +263,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + "command." - check(customChecks(name, MojangNamespace)) + check { customChecks(name, MojangNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -277,7 +286,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Plasma mappings, you can use the " + "`plasma` command." - check(customChecks(name, PlasmaNamespace)) + check { customChecks(name, PlasmaNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -294,7 +303,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Plasma mappings, you can use the " + "`plasma` command." - check(customChecks(name, PlasmaNamespace)) + check { customChecks(name, PlasmaNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -311,7 +320,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Plasma mappings, you can use the " + "`plasma` command." - check(customChecks(name, PlasmaNamespace)) + check { customChecks(name, PlasmaNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -338,7 +347,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarn mappings, you can use the `yarn` " + "command." - check(customChecks(name, YarnNamespace)) + check { customChecks(name, YarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -364,7 +373,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarn mappings, you can use the `yarn` " + "command." - check(customChecks(name, YarnNamespace)) + check { customChecks(name, YarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -390,7 +399,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarn mappings, you can use the `yarn` " + "command." - check(customChecks(name, YarnNamespace)) + check { customChecks(name, YarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -418,7 +427,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarrn mappings, you can use the " + "`yarrn` command." - check(customChecks(name, YarrnNamespace)) + check { customChecks(name, YarrnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -435,7 +444,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarrn mappings, you can use the " + "`yarrn` command." - check(customChecks(name, YarrnNamespace)) + check { customChecks(name, YarrnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -452,7 +461,7 @@ class MappingsExtension : Extension() { "For more information or a list of versions for Yarrn mappings, you can use the " + "`yarrn` command." - check(customChecks(name, YarrnNamespace)) + check { customChecks(name, YarrnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -472,7 +481,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for Legacy Yarn mappings." - check(customChecks(name, LegacyYarnNamespace)) + check { customChecks(name, LegacyYarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -537,7 +546,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for MCP mappings." - check(customChecks(name, MCPNamespace)) + check { customChecks(name, MCPNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -602,7 +611,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for Mojang mappings." - check(customChecks(name, MojangNamespace)) + check { customChecks(name, MojangNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -669,7 +678,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for Plasma mappings." - check(customChecks(name, PlasmaNamespace)) + check { customChecks(name, PlasmaNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -734,7 +743,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for Yarn mappings." - check(customChecks(name, YarnNamespace)) + check { customChecks(name, YarnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -827,7 +836,7 @@ class MappingsExtension : Extension() { description = "Get information and a list of supported versions for Yarrn mappings." - check(customChecks(name, YarrnNamespace)) + check { customChecks(name, YarrnNamespace) } check(categoryCheck, channelCheck, guildCheck) // Default checks action { @@ -1217,18 +1226,22 @@ class MappingsExtension : Extension() { private suspend fun getTimeout() = builder.config.getTimeout() - private suspend fun customChecks(command: String, namespace: Namespace): Check<*> { - val allChecks = builder.commandChecks.map { it.invoke(command) }.toMutableList() - val allNamespaceChecks = builder.namespaceChecks.map { it.invoke(namespace) }.toMutableList() + private suspend fun CheckContext.customChecks(command: String, namespace: Namespace) { + builder.commandChecks.forEach { + it(command)() - var rootCheck = allChecks.removeFirstOrNull() - ?: allNamespaceChecks.removeFirstOrNull() - ?: return { pass() } + if (!passed) { + return + } + } - allChecks.forEach { rootCheck = rootCheck and it } - allNamespaceChecks.forEach { rootCheck = rootCheck and it } + builder.namespaceChecks.forEach { + it(namespace)() - return rootCheck + if (!passed) { + return + } + } } companion object { diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/builders/ExtMappingsBuilder.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/builders/ExtMappingsBuilder.kt index 25f9694cca..9e01be2484 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/builders/ExtMappingsBuilder.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/builders/ExtMappingsBuilder.kt @@ -12,18 +12,18 @@ class ExtMappingsBuilder { var config: MappingsConfigAdapter = TomlMappingsConfig() /** List of checks to apply against the name of the command. **/ - val commandChecks: MutableList Check<*>> = mutableListOf() + val commandChecks: MutableList Check> = mutableListOf() /** List of checks to apply against the namespace corresponding with the command. **/ - val namespaceChecks: MutableList Check<*>> = mutableListOf() + val namespaceChecks: MutableList Check> = mutableListOf() /** Register a check that applies against the name of a command, and its message creation event. **/ fun commandCheck(check: suspend (String) -> Check) { - commandChecks.add(check as (suspend (String) -> Check<*>)) + commandChecks.add(check) } /** Register a check that applies against the mappings namespace for a command, and its message creation event. **/ fun namespaceCheck(check: suspend (Namespace) -> Check) { - namespaceChecks.add(check as (suspend (Namespace) -> Check<*>)) + namespaceChecks.add(check) } } diff --git a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 860077ad20..cbde7e3c57 100644 --- a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.checks.isNotbot +import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.modules.extra.mappings.extMappings import com.kotlindiscord.kord.extensions.utils.env import me.shedaniel.linkie.namespaces.YarnNamespace @@ -12,7 +12,7 @@ suspend fun main() { koinLogLevel = Level.DEBUG chatCommands { - check(isNotbot) + check { isNotBot() } } applicationCommands { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt index 7852b87e11..19791fbacd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelChecks.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.CategoryBehavior import dev.kord.core.behavior.channel.ChannelBehavior @@ -21,7 +21,11 @@ import mu.KotlinLogging * * @param builder Lambda returning the channel to compare to. */ -public fun inChannel(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.inChannel(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val eventChannel = channelFor(event) @@ -57,7 +61,11 @@ public fun inChannel(builder: suspend (T) -> ChannelBehavior): Check * * @param builder Lambda returning the channel to compare to. */ -public fun notInChannel(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.notInChannel(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val eventChannel = channelFor(event) @@ -93,7 +101,11 @@ public fun notInChannel(builder: suspend (T) -> ChannelBehavior): Ch * * @param builder Lambda returning the category to compare to. */ -public fun inCategory(builder: suspend (T) -> CategoryBehavior): Check = { +public suspend fun CheckContext.inCategory(builder: suspend (T) -> CategoryBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inCategory") val eventChannel = topChannelFor(event) @@ -130,7 +142,11 @@ public fun inCategory(builder: suspend (T) -> CategoryBehavior): Che * * @param builder Lambda returning the category to compare to. */ -public fun notInCategory(builder: suspend (T) -> CategoryBehavior): Check = { +public suspend fun CheckContext.notInCategory(builder: suspend (T) -> CategoryBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInCategory") val eventChannel = topChannelFor(event) @@ -167,7 +183,11 @@ public fun notInCategory(builder: suspend (T) -> CategoryBehavior): * * @param builder Lambda returning the channel to compare to. */ -public fun channelHigher(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.channelHigher(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigher") val eventChannel = channelFor(event) @@ -203,7 +223,11 @@ public fun channelHigher(builder: suspend (T) -> ChannelBehavior): C * * @param builder Lambda returning the channel to compare to. */ -public fun channelLower(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.channelLower(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLower") val eventChannel = channelFor(event) @@ -239,7 +263,11 @@ public fun channelLower(builder: suspend (T) -> ChannelBehavior): Ch * * @param builder Lambda returning the channel to compare to. */ -public fun channelHigherOrEqual(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.channelHigherOrEqual(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigherOrEqual") val eventChannel = channelFor(event) @@ -275,7 +303,11 @@ public fun channelHigherOrEqual(builder: suspend (T) -> ChannelBehav * * @param builder Lambda returning the channel to compare to. */ -public fun channelLowerOrEqual(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.channelLowerOrEqual(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLowerOrEqual") val eventChannel = channelFor(event) @@ -316,7 +348,11 @@ public fun channelLowerOrEqual(builder: suspend (T) -> ChannelBehavi * * @param id Channel snowflake to compare to. */ -public fun inChannel(id: Snowflake): Check = { +public suspend fun CheckContext.inChannel(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val channel = event.kord.getChannel(id) @@ -325,7 +361,7 @@ public fun inChannel(id: Snowflake): Check = { fail() } else { - inChannel { channel }() + inChannel { channel } } } @@ -337,7 +373,11 @@ public fun inChannel(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun notInChannel(id: Snowflake): Check = { +public suspend fun CheckContext.notInChannel(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val channel = event.kord.getChannel(id) @@ -346,7 +386,7 @@ public fun notInChannel(id: Snowflake): Check = { pass() } else { - notInChannel { channel }() + notInChannel { channel } } } @@ -358,7 +398,11 @@ public fun notInChannel(id: Snowflake): Check = { * * @param id Category snowflake to compare to. */ -public fun inCategory(id: Snowflake): Check = { +public suspend fun CheckContext.inCategory(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inCategory") val category = event.kord.getChannelOf(id) @@ -367,7 +411,7 @@ public fun inCategory(id: Snowflake): Check = { fail() } else { - inCategory { category }() + inCategory { category } } } @@ -379,7 +423,11 @@ public fun inCategory(id: Snowflake): Check = { * * @param id Category snowflake to compare to. */ -public fun notInCategory(id: Snowflake): Check = { +public suspend fun CheckContext.notInCategory(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInCategory") val category = event.kord.getChannelOf(id) @@ -388,7 +436,7 @@ public fun notInCategory(id: Snowflake): Check = { pass() } else { - notInCategory { category }() + notInCategory { category } } } @@ -400,7 +448,11 @@ public fun notInCategory(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun channelHigher(id: Snowflake): Check = { +public suspend fun CheckContext.channelHigher(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigher") val channel = event.kord.getChannel(id) @@ -409,7 +461,7 @@ public fun channelHigher(id: Snowflake): Check = { fail() } else { - channelHigher { channel }() + channelHigher { channel } } } @@ -421,7 +473,11 @@ public fun channelHigher(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun channelLower(id: Snowflake): Check = { +public suspend fun CheckContext.channelLower(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLower") val channel = event.kord.getChannel(id) @@ -430,7 +486,7 @@ public fun channelLower(id: Snowflake): Check = { fail() } else { - channelLower { channel }() + channelLower { channel } } } @@ -442,7 +498,11 @@ public fun channelLower(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun channelHigherOrEqual(id: Snowflake): Check = { +public suspend fun CheckContext.channelHigherOrEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelHigherOrEqual") val channel = event.kord.getChannel(id) @@ -451,7 +511,7 @@ public fun channelHigherOrEqual(id: Snowflake): Check = { fail() } else { - channelHigherOrEqual { channel }() + channelHigherOrEqual { channel } } } @@ -463,7 +523,11 @@ public fun channelHigherOrEqual(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun channelLowerOrEqual(id: Snowflake): Check = { +public suspend fun CheckContext.channelLowerOrEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelLowerOrEqual") val channel = event.kord.getChannel(id) @@ -472,7 +536,7 @@ public fun channelLowerOrEqual(id: Snowflake): Check = { fail() } else { - channelLowerOrEqual { channel }() + channelLowerOrEqual { channel } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt index a6cc233a07..20134f7c2d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/ChannelTypeChecks.kt @@ -2,18 +2,11 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.utils.getKoin +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.utils.translate import dev.kord.common.entity.ChannelType import dev.kord.core.event.Event import mu.KotlinLogging -import java.util.* - -private val defaultLocale: Locale - get() = - getKoin().get().i18nBuilder.defaultLocale /** * Check asserting that the channel an [Event] fired in is of a given set of types. @@ -23,7 +16,11 @@ private val defaultLocale: Locale * * @param channelTypes The channel types to compare to. */ -public fun channelType(vararg channelTypes: ChannelType): Check<*> = { +public suspend fun CheckContext<*>.channelType(vararg channelTypes: ChannelType) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.channelType") val eventChannel = channelFor(event) @@ -59,7 +56,11 @@ public fun channelType(vararg channelTypes: ChannelType): Check<*> = { * * @param channelTypes The channel types to compare to. */ -public fun notChannelType(vararg channelTypes: ChannelType): Check<*> = { +public suspend fun CheckContext<*>.notChannelType(vararg channelTypes: ChannelType) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notChannelType") val eventChannel = channelFor(event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt deleted file mode 100644 index 648115d5ee..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckCombinators.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.kotlindiscord.kord.extensions.checks - -import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.checks.types.CheckContext -import dev.kord.core.event.Event -import mu.KotlinLogging - -/** - * Special check that passes if any of the given checks pass. - * - * You can think of this as an `or` operation - pass it a bunch of checks, and - * this one will return `true` if any of them pass. - * - * @param checks Two or more checks to combine. - * @return Whether any of the checks passed. - */ -public fun or(vararg checks: Check): Check = { - val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.or") - - val contexts = checks.map { - val context = CheckContext(event, locale) - - it(context) - context - } - - if (contexts.any { it.passed }) { - logger.passed() - - pass() - } else { - logger.failed("None of the given checks passed") - - val failedContext = contexts.firstOrNull { !it.passed } - - if (failedContext != null) { - fail(failedContext.message) - } else { - fail() - } - } -} - -/** Infix-function version of [or]. **/ -public infix fun (Check).or(other: Check): Check = or(this, other) - -/** - * Special check that passes if all of the given checks pass. - * - * You can think of this as an `and` operation - pass it a bunch of checks, and - * this one will return `true` if they all pass. - * - * Don't use this unless you're already using combinators. The `check` functions - * can simply be passed multiple checks. - * - * @param checks Two or more checks to combine. - * @return Whether all of the checks passed. - */ -public fun and(vararg checks: Check): Check = { - val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.and") - - val contexts = checks.map { - val context = CheckContext(event, locale) - - it(context) - context - } - - if (contexts.all { it.passed }) { - logger.passed() - - pass() - } else { - logger.failed("At least one of the given checks failed") - - val failedContext = contexts.firstOrNull { !it.passed } - - if (failedContext != null) { - fail(failedContext.message) - } else { - fail() - } - } -} - -/** Infix-function version of [and]. **/ -public infix fun (Check).and(other: Check): Check = and(this, other) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt index 87f11dbee8..0880d44021 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/GuildChecks.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.GuildBehavior import dev.kord.core.event.Event @@ -15,7 +15,11 @@ import mu.KotlinLogging * that fired within a guild the bot doesn't have access to, or that it can't get the GuildBehavior for (for * example, due to a niche Kord configuration). */ -public val anyGuild: Check<*> = { +public suspend fun CheckContext<*>.anyGuild() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.anyGuild") if (guildFor(event) != null) { @@ -38,7 +42,11 @@ public val anyGuild: Check<*> = { * that fired within a guild the bot doesn't have access to, or that it can't get the GuildBehavior for (for * example, due to a niche Kord configuration). */ -public val noGuild: Check<*> = { +public suspend fun CheckContext<*>.noGuild() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.noGuild") if (guildFor(event) == null) { @@ -64,7 +72,11 @@ public val noGuild: Check<*> = { * * @param builder Lambda returning the guild to compare to. */ -public fun inGuild(builder: suspend (T) -> GuildBehavior): Check = { +public suspend fun CheckContext.inGuild(builder: suspend (T) -> GuildBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inGuild") val eventGuild = guildFor(event)?.asGuildOrNull() @@ -100,7 +112,11 @@ public fun inGuild(builder: suspend (T) -> GuildBehavior): Check * * @param builder Lambda returning the guild to compare to. */ -public fun notInGuild(builder: suspend (T) -> GuildBehavior): Check = { +public suspend fun CheckContext.notInGuild(builder: suspend (T) -> GuildBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInGuild") val eventGuild = guildFor(event)?.asGuild() @@ -140,7 +156,11 @@ public fun notInGuild(builder: suspend (T) -> GuildBehavior): Check< * * @param id Guild snowflake to compare to. */ -public fun inGuild(id: Snowflake): Check = { +public suspend fun CheckContext.inGuild(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inGuild") val guild = event.kord.getGuild(id) @@ -149,7 +169,7 @@ public fun inGuild(id: Snowflake): Check = { fail() } else { - inGuild { guild }() + inGuild { guild } } } @@ -161,7 +181,11 @@ public fun inGuild(id: Snowflake): Check = { * * @param id Guild snowflake to compare to. */ -public fun notInGuild(id: Snowflake): Check = { +public suspend fun CheckContext.notInGuild(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInGuild") val guild = event.kord.getGuild(id) @@ -170,7 +194,7 @@ public fun notInGuild(id: Snowflake): Check = { pass() } else { - notInGuild { guild }() + notInGuild { guild } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt index 7827e68365..9b0c7a7b34 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MemberChecks.kt @@ -2,9 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder -import com.kotlindiscord.kord.extensions.checks.types.Check -import com.kotlindiscord.kord.extensions.utils.getKoin +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.utils.hasPermission import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate @@ -12,10 +10,6 @@ import dev.kord.common.entity.Permission import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.event.Event import mu.KotlinLogging -import java.util.* - -private val defaultLocale: Locale - get() = getKoin().get().i18nBuilder.defaultLocale /** * Check asserting that the user an [Event] fired for has a given permission, or the Administrator permission. @@ -25,7 +19,11 @@ private val defaultLocale: Locale * * @param perm The permission to check for. */ -public fun hasPermission(perm: Permission): Check<*> = { +public suspend fun CheckContext<*>.hasPermission(perm: Permission) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.hasPermission") val channel = channelFor(event) as? GuildChannel val member = memberFor(event) @@ -70,7 +68,11 @@ public fun hasPermission(perm: Permission): Check<*> = { * * @param perm The permission to check for. */ -public fun notHasPermission(perm: Permission): Check<*> = { +public suspend fun CheckContext<*>.notHasPermission(perm: Permission) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notHasPermission") val channel = channelFor(event) as? GuildChannel val member = memberFor(event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt index fd30fd79c0..88fe60849c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/MiscChecks.kt @@ -1,17 +1,18 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior import dev.kord.core.event.Event import mu.KotlinLogging -import java.util.* /** * Check asserting the user for an [Event] is a bot. Will fail if the event doesn't concern a user. - * - * @param event Event object to check. */ -public val isBot: Check<*> = { +public suspend fun CheckContext<*>.isBot() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.isBot") val user = userFor(event)?.asUserOrNull() @@ -34,10 +35,12 @@ public val isBot: Check<*> = { /** * Check asserting the user for an [Event] is **not** a bot. Will fail if the event doesn't concern a user. - * - * @param event Event object to check. */ -public val isNotbot: Check<*> = { +public suspend fun CheckContext<*>.isNotBot() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.isNotBot") val user = userFor(event)?.asUserOrNull() @@ -61,7 +64,11 @@ public val isNotbot: Check<*> = { /** * Check asserting that the event was triggered within a thread. */ -public val isInThread: Check<*> = { +public suspend fun CheckContext<*>.isInThread() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.isInThread") val channel = channelFor(event)?.asChannelOrNull() @@ -86,7 +93,11 @@ public val isInThread: Check<*> = { * Check asserting that the event was **not** triggered within a thread, including events that don't concern any * specific channel. */ -public val isNotInThread: Check<*> = { +public suspend fun CheckContext<*>.isNotInThread() { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.isNotInThread") val channel = channelFor(event)?.asChannelOrNull() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt index e351f5bf64..bb316559a8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/RoleChecks.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.utils.getTopRole import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.RoleBehavior @@ -20,7 +20,11 @@ import mu.KotlinLogging * * @param builder Lambda returning the role to compare to. */ -public fun hasRole(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.hasRole(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.hasRole") val member = memberFor(event) @@ -56,7 +60,11 @@ public fun hasRole(builder: suspend (T) -> RoleBehavior): Check = * * @param builder Lambda returning the role to compare to. */ -public fun notHasRole(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.notHasRole(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notHasRole") val member = memberFor(event) @@ -92,7 +100,11 @@ public fun notHasRole(builder: suspend (T) -> RoleBehavior): Check topRoleEqual(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleEqual(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleEqual") val member = memberFor(event) @@ -144,7 +156,11 @@ public fun topRoleEqual(builder: suspend (T) -> RoleBehavior): Check * * @param builder Lambda returning the role to compare to. */ -public fun topRoleNotEqual(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleNotEqual(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleNotEqual") val member = memberFor(event) @@ -190,7 +206,11 @@ public fun topRoleNotEqual(builder: suspend (T) -> RoleBehavior): Ch * * @param builder Lambda returning the role to compare to. */ -public fun topRoleHigher(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleHigher(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigher") val member = memberFor(event) @@ -244,7 +264,11 @@ public fun topRoleHigher(builder: suspend (T) -> RoleBehavior): Chec * * @param builder Lambda returning the role to compare to. */ -public fun topRoleLower(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleLower(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLower") val member = memberFor(event) @@ -292,7 +316,11 @@ public fun topRoleLower(builder: suspend (T) -> RoleBehavior): Check * * @param builder Lambda returning the role to compare to. */ -public fun topRoleHigherOrEqual(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleHigherOrEqual(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigherOrEqual") val member = memberFor(event) @@ -347,7 +375,11 @@ public fun topRoleHigherOrEqual(builder: suspend (T) -> RoleBehavior * * @param builder Lambda returning the role to compare to. */ -public fun topRoleLowerOrEqual(builder: suspend (T) -> RoleBehavior): Check = { +public suspend fun CheckContext.topRoleLowerOrEqual(builder: suspend (T) -> RoleBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLowerOrEqual") val member = memberFor(event) @@ -398,7 +430,11 @@ public fun topRoleLowerOrEqual(builder: suspend (T) -> RoleBehavior) * * @param id Role snowflake to compare to. */ -public fun hasRole(id: Snowflake): Check = { +public suspend fun CheckContext.hasRole(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.hasRole") val role = guildFor(event)?.getRoleOrNull(id) @@ -407,7 +443,7 @@ public fun hasRole(id: Snowflake): Check = { fail() } else { - hasRole { role }() + hasRole { role } } } @@ -419,7 +455,11 @@ public fun hasRole(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun notHasRole(id: Snowflake): Check = { +public suspend fun CheckContext.notHasRole(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notHasRole") val role = guildFor(event)?.getRoleOrNull(id) @@ -428,7 +468,7 @@ public fun notHasRole(id: Snowflake): Check = { pass() } else { - notHasRole { role }() + notHasRole { role } } } @@ -440,7 +480,11 @@ public fun notHasRole(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleEqual(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -449,7 +493,7 @@ public fun topRoleEqual(id: Snowflake): Check = { fail() } else { - topRoleEqual { role }() + topRoleEqual { role } } } @@ -461,7 +505,11 @@ public fun topRoleEqual(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleNotEqual(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleNotEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleNotEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -470,7 +518,7 @@ public fun topRoleNotEqual(id: Snowflake): Check = { pass() } else { - topRoleNotEqual { role }() + topRoleNotEqual { role } } } @@ -482,7 +530,11 @@ public fun topRoleNotEqual(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleHigher(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleHigher(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigher") val role = guildFor(event)?.getRoleOrNull(id) @@ -491,7 +543,7 @@ public fun topRoleHigher(id: Snowflake): Check = { fail() } else { - topRoleHigher { role }() + topRoleHigher { role } } } @@ -505,7 +557,11 @@ public fun topRoleHigher(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleLower(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleLower(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLower") val role = guildFor(event)?.getRoleOrNull(id) @@ -514,7 +570,7 @@ public fun topRoleLower(id: Snowflake): Check = { fail() } else { - topRoleLower { role }() + topRoleLower { role } } } @@ -527,7 +583,11 @@ public fun topRoleLower(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleHigherOrEqual(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleHigherOrEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleHigherOrEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -536,7 +596,7 @@ public fun topRoleHigherOrEqual(id: Snowflake): Check = { fail() } else { - topRoleHigherOrEqual { role }() + topRoleHigherOrEqual { role } } } @@ -551,7 +611,11 @@ public fun topRoleHigherOrEqual(id: Snowflake): Check = { * * @param id Role snowflake to compare to. */ -public fun topRoleLowerOrEqual(id: Snowflake): Check = { +public suspend fun CheckContext.topRoleLowerOrEqual(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.topRoleLowerOrEqual") val role = guildFor(event)?.getRoleOrNull(id) @@ -560,7 +624,7 @@ public fun topRoleLowerOrEqual(id: Snowflake): Check = { fail() } else { - topRoleLowerOrEqual { role }() + topRoleLowerOrEqual { role } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt index 06a967f0e1..8a10a83364 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.entity.channel.thread.ThreadChannel @@ -20,7 +20,11 @@ import mu.KotlinLogging * * @param builder Lambda returning the channel to compare to. */ -public fun inTopChannel(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.inTopChannel(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") val eventChannel = topChannelFor(event) @@ -57,7 +61,11 @@ public fun inTopChannel(builder: suspend (T) -> ChannelBehavior): Ch * * @param builder Lambda returning the channel to compare to. */ -public fun notInTopChannel(builder: suspend (T) -> ChannelBehavior): Check = { +public suspend fun CheckContext.notInTopChannel(builder: suspend (T) -> ChannelBehavior) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") val eventChannel = topChannelFor(event) @@ -98,7 +106,11 @@ public fun notInTopChannel(builder: suspend (T) -> ChannelBehavior): * * @param id Channel snowflake to compare to. */ -public fun inTopChannel(id: Snowflake): Check = { +public suspend fun CheckContext.inTopChannel(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.inChannel") var channel = event.kord.getChannel(id) @@ -111,7 +123,7 @@ public fun inTopChannel(id: Snowflake): Check = { fail() } else { - inChannel { channel }() + inChannel { channel } } } @@ -124,7 +136,11 @@ public fun inTopChannel(id: Snowflake): Check = { * * @param id Channel snowflake to compare to. */ -public fun notInTopChannel(id: Snowflake): Check = { +public suspend fun CheckContext.notInTopChannel(id: Snowflake) { + if (!passed) { + return + } + val logger = KotlinLogging.logger("com.kotlindiscord.kord.extensions.checks.notInChannel") var channel = event.kord.getChannel(id) @@ -137,7 +153,7 @@ public fun notInTopChannel(id: Snowflake): Check = { pass() } else { - notInChannel { channel }() + notInChannel { channel } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index e6698f5b6d..d09e7ff240 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -6,7 +6,6 @@ import dev.kord.core.event.Event import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* -import kotlin.jvm.Throws /** * Class representing the context for a check. This allows the storage of check status and a message for the users. @@ -75,6 +74,45 @@ public class CheckContext(public val event: T, public val locale: public suspend fun failIfNot(message: String? = null, callback: suspend () -> Boolean): Boolean = failIfNot(callback(), message) + /** + * If [value] is `true`, mark this check as having passed. + * + * Returns `true` if the check was marked as having passed, `false` otherwise. + */ + public fun passIf(value: Boolean): Boolean { + if (value) { + pass() + + return true + } + + return false + } + + /** + * If [callback] returns `true`, mark this check as having passed. + * + * Returns `true` if the check was marked as having passed, `false` otherwise. + */ + public suspend fun passIf(callback: suspend () -> Boolean): Boolean = + passIf(callback()) + + /** + * If [value] is `true`, mark this check as having passed. + * + * Returns `true` if the check was marked as having passed, `false` otherwise. + */ + public fun passIfNot(value: Boolean): Boolean = + passIf(!value) + + /** + * If [callback] returns `true`, mark this check as having passed. + * + * Returns `true` if the check was marked as having passed, `false` otherwise. + */ + public suspend fun passIfNot(callback: suspend () -> Boolean): Boolean = + passIfNot(callback()) + /** Call the given block if the Boolean receiver is `true`. **/ public inline fun Boolean.whenTrue(body: () -> T?): T? { if (this) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 0ce6ad2abf..3a12533d1a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -249,44 +249,6 @@ public open class ChatCommand( checkList.add(check) } - /** - * Define a simple Boolean check which must pass for the command to be executed. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * A command may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to this command. - */ - public open fun booleanCheck(vararg checks: suspend (MessageCreateEvent) -> Boolean) { - checks.forEach(::booleanCheck) - } - - /** - * Overloaded simple Boolean check function to allow for DSL syntax. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * @param check Check to apply to this command. - */ - public open fun booleanCheck(check: suspend (MessageCreateEvent) -> Boolean) { - check { - if (check(event)) { - pass() - } else { - fail() - } - } - } - // endregion /** Run checks with the provided [MessageCreateEvent]. Return false if any failed, true otherwise. **/ @@ -373,7 +335,8 @@ public open class ChatCommand( * * @param event The message creation event. * @param commandName The name used to invoke this command. - * @param args Array of command arguments. + * @param parser Parser used to parse the command's arguments, available for further parsing. + * @param argString Original string containing the command's arguments. * @param skipChecks Whether to skip testing the command's checks. */ public open suspend fun call( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt index 3ad7b8d13a..5791554b5c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt @@ -109,44 +109,6 @@ public open class EventHandler( */ public fun check(check: Check): Boolean = checkList.add(check) - /** - * Define a simple Boolean check which must pass for the event handler to be executed. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * An event handler may have multiple checks - all checks must pass for the command to be executed. - * Checks will be run in the order that they're defined. - * - * This function can be used DSL-style with a given body, or it can be passed one or more - * predefined functions. See the samples for more information. - * - * @param checks Checks to apply to this event handler. - */ - public open fun booleanCheck(vararg checks: suspend (T) -> Boolean) { - checks.forEach(::booleanCheck) - } - - /** - * Overloaded simple Boolean check function to allow for DSL syntax. - * - * Boolean checks are simple wrappers around the regular check system, allowing you to define a basic check that - * takes an event object and returns a [Boolean] representing whether it passed. This style of check does not have - * the same functionality as a regular check, and cannot return a message. - * - * @param check Check to apply to this event handler. - */ - public open fun booleanCheck(check: suspend (T) -> Boolean) { - check { - if (check(event)) { - pass() - } else { - fail() - } - } - } - // endregion /** diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 92ebc8ca96..1edd13f7e2 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.test.bot import com.kotlindiscord.kord.extensions.ExtensibleBot -import com.kotlindiscord.kord.extensions.checks.isNotbot +import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.utils.env import org.koin.core.logger.Level @@ -13,7 +13,7 @@ suspend fun main() { defaultPrefix = "?" enabled = true - check(isNotbot) + check { isNotBot() } prefix { default -> if (guildId?.asString == "787452339908116521") { From e5c3e151ae970968967bb1ad08a452066a440e4a Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 15:45:06 +0100 Subject: [PATCH 031/131] Unsafe API module --- .../slash/EphemeralSlashCommand.kt | 6 +- .../application/slash/PublicSlashCommand.kt | 6 +- .../application/slash/SlashCommand.kt | 3 +- .../commands/application/slash/SlashGroup.kt | 3 +- .../kord/extensions/extensions/Extension.kt | 2 +- .../EphemeralInteractionContext.kt | 12 +- modules/unsafe/build.gradle.kts | 171 ++++++++++++++++++ .../modules/unsafe/annotations/UnsafeAPI.kt | 10 + .../unsafe/commands/UnsafeMessageCommand.kt | 73 ++++++++ .../unsafe/commands/UnsafeSlashCommand.kt | 114 ++++++++++++ .../unsafe/commands/UnsafeUserCommand.kt | 73 ++++++++ .../contexts/UnsafeMessageCommandContext.kt | 16 ++ .../contexts/UnsafeSlashCommandContext.kt | 17 ++ .../contexts/UnsafeUserCommandContext.kt | 17 ++ .../unsafe/converters}/UnionConverter.kt | 7 +- .../modules/unsafe/extensions/_Commands.kt | 161 +++++++++++++++++ .../unsafe/extensions/_SlashCommands.kt | 153 ++++++++++++++++ .../types/InitialMessageCommandResponse.kt | 31 ++++ .../types/InitialSlashCommandResponse.kt | 31 ++++ .../types/InitialUserCommandResponse.kt | 31 ++++ .../unsafe/types/UnsafeInteractionContext.kt | 117 ++++++++++++ settings.gradle.kts | 1 + 22 files changed, 1044 insertions(+), 11 deletions(-) create mode 100644 modules/unsafe/build.gradle.kts create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/annotations/UnsafeAPI.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt rename {kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl => modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters}/UnionConverter.kt (97%) create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt create mode 100644 modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index c733b702e6..16a2ae87ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -12,7 +12,7 @@ import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder -public typealias InitialEphemeralChatResponseBuilder = +public typealias InitialEphemeralSlashResponseBuilder = (suspend EphemeralInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? /** Ephemeral slash command. **/ @@ -24,10 +24,10 @@ public class EphemeralSlashCommand( public override val parentGroup: SlashGroup? = null ) : SlashCommand, A>(extension) { /** @suppress Internal guilder **/ - public var initialResponseBuilder: InitialEphemeralChatResponseBuilder = null + public var initialResponseBuilder: InitialEphemeralSlashResponseBuilder = null /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialEphemeralChatResponseBuilder) { + public fun initialResponse(body: InitialEphemeralSlashResponseBuilder) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index c0a6d9afd7..9a49ff4718 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -12,7 +12,7 @@ import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder -public typealias InitialPublicChatResponseBuilder = +public typealias InitialPublicSlashResponseBehavior = (suspend PublicInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? /** Public slash command. **/ @@ -24,10 +24,10 @@ public class PublicSlashCommand( public override val parentGroup: SlashGroup? = null ) : SlashCommand, A>(extension) { /** @suppress Internal guilder **/ - public var initialResponseBuilder: InitialPublicChatResponseBuilder = null + public var initialResponseBuilder: InitialPublicSlashResponseBehavior = null /** Call this to open with a response, omit it to ack instead. **/ - public fun initialResponse(body: InitialPublicChatResponseBuilder) { + public fun initialResponse(body: InitialPublicSlashResponseBehavior) { initialResponseBuilder = body } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 0e9a084ae9..74446ed1f2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -36,7 +36,8 @@ public abstract class SlashCommand, A : Arguments> public open val parentCommand: SlashCommand<*, *>? = null, public open val parentGroup: SlashGroup? = null ) : ApplicationCommand(extension) { - internal val logger: KLogger = KotlinLogging.logger {} + /** @suppress **/ + public val logger: KLogger = KotlinLogging.logger {} /** Command description, as displayed on Discord. **/ public open lateinit var description: String diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt index 0dfd9c1c33..324cef4404 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt @@ -14,7 +14,8 @@ public class SlashGroup( public val name: String, public val parent: SlashCommand<*, *> ) { - internal val logger: KLogger = KotlinLogging.logger {} + /** @suppress **/ + public val logger: KLogger = KotlinLogging.logger {} /** List of subcommands belonging to this group. **/ public val subCommands: MutableList> = mutableListOf() 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 927fdfb7ca..c36ba12635 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 @@ -43,7 +43,7 @@ public abstract class Extension : KoinComponent { internal val chatCommandRegistry: ChatCommandRegistry by inject() /** Slash command registry. **/ - internal val applicationCommandRegistry: ApplicationCommandRegistry by inject() + public val applicationCommandRegistry: ApplicationCommandRegistry by inject() /** * The name of this extension. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt index a761f2ee7b..924bea1936 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt @@ -5,8 +5,11 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUpEphemeral +import dev.kord.core.behavior.interaction.followUpPublic import dev.kord.core.entity.interaction.EphemeralFollowupMessage +import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder import java.util.* @@ -19,12 +22,19 @@ public interface EphemeralInteractionContext { /** * Respond to the current interaction with an ephemeral followup. * - * **Note:** Calling this twice will result in a public followup! + * **Note:** Calling this twice (or at all after [edit]) will result in a public followup! */ public suspend inline fun EphemeralInteractionContext.respond( builder: EphemeralFollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) +/** + * Respond to the current interaction with a public followup. + */ +public suspend inline fun EphemeralInteractionContext.respondPublic( + builder: PublicFollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage = interactionResponse.followUpPublic(builder) + /** * Edit the current interaction's response. */ diff --git a/modules/unsafe/build.gradle.kts b/modules/unsafe/build.gradle.kts new file mode 100644 index 0000000000..5ee50b7526 --- /dev/null +++ b/modules/unsafe/build.gradle.kts @@ -0,0 +1,171 @@ +import java.io.ByteArrayOutputStream +import java.net.URL + +plugins { + `maven-publish` + signing + + kotlin("jvm") + + id("com.google.devtools.ksp") + id("io.gitlab.arturbosch.detekt") + id("org.jetbrains.dokka") +} + +dependencies { + implementation(libs.kotlin.stdlib) + implementation(project(":kord-extensions")) + + detektPlugins(libs.detekt) + + testImplementation(libs.groovy) // For logback config + testImplementation(libs.junit) + testImplementation(libs.logback) + + ksp(project(":annotation-processor")) +} + +kotlin { + explicitApi() +} + +val sourceJar = task("sourceJar", Jar::class) { + dependsOn(tasks["classes"]) + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) +} + +val javadocJar = task("javadocJar", Jar::class) { + dependsOn("dokkaJavadoc") + archiveClassifier.set("javadoc") + from(tasks.javadoc) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +kotlin { + explicitApi() +} + +sourceSets { + main { + java { + srcDir(file("$buildDir/generated/ksp/main/kotlin/")) + } + } + + test { + java { + srcDir(file("$buildDir/generated/ksp/test/kotlin/")) + } + } +} + +detekt { + buildUponDefaultConfig = true + config = files("../../detekt.yml") + + autoCorrect = true +} + +publishing { + repositories { + maven { + name = "KotDis" + + url = if (project.version.toString().contains("SNAPSHOT")) { + uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") + } else { + uri("https://maven.kotlindiscord.com/repository/maven-releases/") + } + + credentials { + username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") + password = project.findProperty("kotdis.password") as String? + ?: System.getenv("KOTLIN_DISCORD_PASSWORD") + } + + version = project.version + } + } + + publications { + create("maven") { + from(components.getByName("java")) + + artifact(sourceJar) + artifact(javadocJar) + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} + +fun runCommand(command: String): String { + val output = ByteArrayOutputStream() + + project.exec { + commandLine(command.split(" ")) + standardOutput = output + } + + return output.toString().trim() +} + +fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 + var gitBranch = "Unknown branch" + + try { + gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") + } catch (t: Throwable) { + println(t) + } + + return gitBranch +} + +tasks.dokkaHtml.configure { + moduleName.set("Kord Extensions: Time4J") + + dokkaSourceSets { + configureEach { + includeNonPublic.set(false) + skipDeprecated.set(false) + + displayName.set("Kord Extensions: Unsafe Module") + includes.from("packages.md") + jdkVersion.set(8) + + sourceLink { + localDirectory.set(file("${project.projectDir}/src/main/kotlin")) + + remoteUrl.set( + URL( + "https://github.com/Kotlin-Discord/kord-extensions/" + + "tree/${getCurrentGitBranch()}/modules/unsafe/src/main/kotlin" + ) + ) + + remoteLineSuffix.set("#L") + } + + externalDocumentationLink { + url.set(URL("http://kordlib.github.io/kord/common/common/")) + } + + externalDocumentationLink { + url.set(URL("http://kordlib.github.io/kord/core/core/")) + } + } + } +} + +tasks.build { + this.finalizedBy(sourceJar, javadocJar) +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/annotations/UnsafeAPI.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/annotations/UnsafeAPI.kt new file mode 100644 index 0000000000..0d2be61ccf --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/annotations/UnsafeAPI.kt @@ -0,0 +1,10 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.annotations + +/** Annotation used to mark unsafe APIs. Should be applied to basically everything in this module. **/ +@RequiresOptIn( + message = "This API is unsafe, and is only intended for advanced use-cases. If you're not entirely sure that " + + "you need to use this, you should look for a safer API that's provided by a different Kord Extensions module." +) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.TYPEALIAS) +public annotation class UnsafeAPI diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt new file mode 100644 index 0000000000..70bcd73560 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -0,0 +1,73 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.modules.unsafe.commands + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeMessageCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialMessageCommandResponse +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** Like a standard message command, but with less safety features. **/ +@UnsafeAPI +public class UnsafeMessageCommand( + extension: Extension +) : MessageCommand(extension) { + /** @suppress **/ + public var initialResponse: InitialMessageCommandResponse = InitialMessageCommandResponse.EphemeralAck + + override suspend fun call(event: MessageCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = when (val r = initialResponse) { + is InitialMessageCommandResponse.EphemeralAck -> event.interaction.acknowledgeEphemeral() + is InitialMessageCommandResponse.PublicAck -> event.interaction.acknowledgePublic() + + is InitialMessageCommandResponse.EphemeralResponse -> event.interaction.respondEphemeral { + r.builder!!(event) + } + + is InitialMessageCommandResponse.PublicResponse -> event.interaction.respondPublic { + r.builder!!(event) + } + } + + val context = UnsafeMessageCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: UnsafeMessageCommandContext, message: String) { + when (context.interactionResponse) { + is PublicInteractionResponseBehavior -> context.respondPublic { content = message } + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + } + } +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt new file mode 100644 index 0000000000..d5b9422074 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -0,0 +1,114 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.modules.unsafe.commands + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashGroup +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeSlashCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.entity.interaction.GroupCommand +import dev.kord.core.entity.interaction.SubCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +/** Like a standard slash command, but with less safety features. **/ +@UnsafeAPI +public class UnsafeSlashCommand( + extension: Extension, + + public override val arguments: (() -> A)? = null, + public override val parentCommand: SlashCommand<*, *>? = null, + public override val parentGroup: SlashGroup? = null +) : SlashCommand, A>(extension) { + /** @suppress **/ + public var initialResponse: InitialSlashCommandResponse = InitialSlashCommandResponse.EphemeralAck + + override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { + val eventCommand = event.interaction.command + + val commandObj: SlashCommand<*, *> = when (eventCommand) { + is SubCommand -> { + val firstSubCommandKey = eventCommand.name + + this.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + is GroupCommand -> { + val firstEventGroupKey = eventCommand.groupName + val group = this.groups[firstEventGroupKey] ?: error("Unknown command group: $firstEventGroupKey") + val firstSubCommandKey = eventCommand.name + + group.subCommands.firstOrNull { it.name == firstSubCommandKey } + ?: error("Unknown subcommand: $firstSubCommandKey") + } + + else -> this + } + + try { + if (!commandObj.runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + val response = when (val r = initialResponse) { + is InitialSlashCommandResponse.EphemeralAck -> event.interaction.acknowledgeEphemeral() + is InitialSlashCommandResponse.PublicAck -> event.interaction.acknowledgePublic() + + is InitialSlashCommandResponse.EphemeralResponse -> event.interaction.respondEphemeral { + r.builder!!(event) + } + + is InitialSlashCommandResponse.PublicResponse -> event.interaction.respondPublic { + r.builder!!(event) + } + } + + val context = UnsafeSlashCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context, this) + + try { + checkBotPerms(context) + + if (arguments != null) { + val args = registry.argumentParser.parse(arguments, context) + + context.populateArgs(args) + } + + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t, this) + } + } + + override suspend fun respondText(context: UnsafeSlashCommandContext, message: String) { + when (context.interactionResponse) { + is PublicInteractionResponseBehavior -> context.respondPublic { content = message } + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + } + } +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt new file mode 100644 index 0000000000..8c21381bb6 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -0,0 +1,73 @@ +@file:Suppress("TooGenericExceptionCaught") + +package com.kotlindiscord.kord.extensions.modules.unsafe.commands + +import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeUserCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialUserCommandResponse +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral +import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.respondEphemeral +import dev.kord.core.behavior.interaction.respondPublic +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** Like a standard user command, but with less safety features. **/ +@UnsafeAPI +public class UnsafeUserCommand( + extension: Extension +) : UserCommand(extension) { + /** @suppress **/ + public var initialResponse: InitialUserCommandResponse = InitialUserCommandResponse.EphemeralAck + + override suspend fun call(event: UserCommandInteractionCreateEvent) { + try { + if (!runChecks(event)) { + return + } + } catch (e: CommandException) { + event.interaction.respondPublic { content = e.reason } + + return + } + + val response = when (val r = initialResponse) { + is InitialUserCommandResponse.EphemeralAck -> event.interaction.acknowledgeEphemeral() + is InitialUserCommandResponse.PublicAck -> event.interaction.acknowledgePublic() + + is InitialUserCommandResponse.EphemeralResponse -> event.interaction.respondEphemeral { + r.builder!!(event) + } + + is InitialUserCommandResponse.PublicResponse -> event.interaction.respondPublic { + r.builder!!(event) + } + } + + val context = UnsafeUserCommandContext(event, this, response) + + context.populate() + + firstSentryBreadcrumb(context) + + try { + checkBotPerms(context) + body(context) + } catch (e: CommandException) { + respondText(context, e.reason) + } catch (t: Throwable) { + handleError(context, t) + } + } + + override suspend fun respondText(context: UnsafeUserCommandContext, message: String) { + when (context.interactionResponse) { + is PublicInteractionResponseBehavior -> context.respondPublic { content = message } + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + } + } +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt new file mode 100644 index 0000000000..2d7e54569d --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt @@ -0,0 +1,16 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.contexts + +import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand +import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.types.UnsafeInteractionContext +import dev.kord.core.behavior.interaction.InteractionResponseBehavior +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +/** Command context for an unsafe message command. **/ +@UnsafeAPI +public class UnsafeMessageCommandContext( + override val event: MessageCommandInteractionCreateEvent, + override val command: MessageCommand, + override val interactionResponse: InteractionResponseBehavior, +) : MessageCommandContext(event, command), UnsafeInteractionContext diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt new file mode 100644 index 0000000000..a87c42bdae --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt @@ -0,0 +1,17 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.contexts + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeSlashCommand +import com.kotlindiscord.kord.extensions.modules.unsafe.types.UnsafeInteractionContext +import dev.kord.core.behavior.interaction.InteractionResponseBehavior +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +/** Command context for an unsafe slash command. **/ +@UnsafeAPI +public class UnsafeSlashCommandContext( + override val event: ChatInputCommandInteractionCreateEvent, + override val command: UnsafeSlashCommand, + override val interactionResponse: InteractionResponseBehavior +) : SlashCommandContext, A>(event, command), UnsafeInteractionContext diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt new file mode 100644 index 0000000000..6a22a5288f --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt @@ -0,0 +1,17 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.contexts + +import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand +import com.kotlindiscord.kord.extensions.commands.application.user.UserCommandContext +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.types.UnsafeInteractionContext +import dev.kord.core.behavior.interaction.InteractionResponseBehavior +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +/** Command context for an unsafe user command. **/ +@UnsafeAPI +public class UnsafeUserCommandContext( + override val event: UserCommandInteractionCreateEvent, + override val command: UserCommand, + override val interactionResponse: InteractionResponseBehavior + +) : UserCommandContext(event, command), UnsafeInteractionContext diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt similarity index 97% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt rename to modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt index 84b0ea5a83..14173285d7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UnionConverter.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt @@ -5,7 +5,7 @@ ConverterToOptional::class ) -package com.kotlindiscord.kord.extensions.commands.converters.impl +package com.kotlindiscord.kord.extensions.modules.unsafe.converters import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.Argument @@ -13,12 +13,14 @@ import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import org.koin.core.component.inject +@UnsafeAPI private typealias GenericConverter = Converter<*, *, *, *> /** @@ -27,6 +29,7 @@ private typealias GenericConverter = Converter<*, *, *, *> * This converter does not support optional or defaulting converters. */ @OptIn(KordPreview::class) +@UnsafeAPI public class UnionConverter( private val converters: Collection, @@ -184,6 +187,7 @@ public class UnionConverter( * * @see UnionConverter */ +@UnsafeAPI public fun Arguments.union( displayName: String, description: String, @@ -217,6 +221,7 @@ public fun Arguments.union( * * @see UnionConverter */ +@UnsafeAPI public fun Arguments.optionalUnion( displayName: String, description: String, diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt new file mode 100644 index 0000000000..4aaa4625b6 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_Commands.kt @@ -0,0 +1,161 @@ +@file:Suppress("StringLiteralDuplication") + +package com.kotlindiscord.kord.extensions.modules.unsafe.extensions + +import com.kotlindiscord.kord.extensions.CommandRegistrationException +import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeMessageCommand +import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeSlashCommand +import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeUserCommand +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +// region: Message commands + +/** Register an unsafe message command, DSL-style. **/ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeMessageCommand( + body: suspend UnsafeMessageCommand.() -> Unit +): UnsafeMessageCommand { + val commandObj = UnsafeMessageCommand(this) + body(commandObj) + + return unsafeMessageCommand(commandObj) +} + +/** Register a custom instance of an unsafe message command. **/ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeMessageCommand( + commandObj: UnsafeMessageCommand +): UnsafeMessageCommand { + try { + commandObj.validate() + messageCommands.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 +} + +// endregion + +// region: Slash commands (Unsafe) + +/** + * DSL function for easily registering an unsafe slash command, with arguments. + * + * Use this in your setup function to register a slash command that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeSlashCommand( + arguments: () -> T, + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(this, arguments, null, null) + body(commandObj) + + return unsafeSlashCommand(commandObj) +} + +/** + * Function for registering a custom unsafe slash command object. + * + * You can use this if you have a custom unsafe slash command subclass you need to register. + * + * @param commandObj UnsafeSlashCommand object to register. + */ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeSlashCommand( + commandObj: UnsafeSlashCommand +): UnsafeSlashCommand { + try { + commandObj.validate() + slashCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + if (applicationCommandRegistry.initialised) { + applicationCommandRegistry.register(commandObj) + } + + return commandObj +} + +/** + * DSL function for easily registering an unsafe slash command, without arguments. + * + * Use this in your setup function to register a slash command that may be executed on Discord. + * + * @param body Builder lambda used for setting up the slash command object. + */ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeSlashCommand( + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(this, null, null, null) + body(commandObj) + + return unsafeSlashCommand(commandObj) +} + +// endregion + +// region: User commands + +/** Register an unsafe user command, DSL-style. **/ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeUserCommand( + body: suspend UnsafeUserCommand.() -> Unit +): UnsafeUserCommand { + val commandObj = UnsafeUserCommand(this) + body(commandObj) + + return unsafeUserCommand(commandObj) +} + +/** Register a custom instance of an unsafe user command. **/ +@ExtensionDSL +@UnsafeAPI +public suspend fun Extension.unsafeUserCommand( + commandObj: UnsafeUserCommand +): UnsafeUserCommand { + try { + commandObj.validate() + userCommands.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 +} +// endregion diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt new file mode 100644 index 0000000000..bcd5c09816 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/extensions/_SlashCommands.kt @@ -0,0 +1,153 @@ +@file:Suppress("StringLiteralDuplication") + +package com.kotlindiscord.kord.extensions.modules.unsafe.extensions + +import com.kotlindiscord.kord.extensions.CommandRegistrationException +import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashGroup +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.commands.UnsafeSlashCommand + +private const val SUBCOMMAND_AND_GROUP_LIMIT: Int = 25 + +// region: Slash commands (Unsafe) + +/** + * DSL function for easily registering an unsafe subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +@UnsafeAPI +public suspend fun SlashCommand<*, *>.unsafeSubCommand( + arguments: () -> T, + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(extension, arguments, parentCommand, parentGroup) + body(commandObj) + + return unsafeSubCommand(commandObj) +} + +/** + * Function for registering a custom unsafe slash command object, for subcommands. + * + * You can use this if you have a custom unsafe slash command subclass you need to register. + * + * @param commandObj UnsafeSlashCommand object to register as a subcommand. + */ +@UnsafeAPI +public fun SlashCommand<*, *>.unsafeSubCommand( + commandObj: UnsafeSlashCommand +): UnsafeSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering an unsafe subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +@UnsafeAPI +public suspend fun SlashCommand<*, *>.unsafeSubCommand( + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(extension, null, parentCommand, parentGroup) + body(commandObj) + + return unsafeSubCommand(commandObj) +} + +// endregion + +// region: Slash groups (Unsafe) + +/** + * DSL function for easily registering an unsafe subcommand, with arguments. + * + * Use this in your setup function to register a subcommand that may be executed on Discord. + * + * @param arguments Arguments builder (probably a reference to the class constructor). + * @param body Builder lambda used for setting up the slash command object. + */ +@UnsafeAPI +public suspend fun SlashGroup.unsafeSubCommand( + arguments: () -> T, + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(parent.extension, arguments, parent, this) + body(commandObj) + + return unsafeSubCommand(commandObj) +} + +/** + * Function for registering a custom unsafe slash command object, for subcommands. + * + * You can use this if you have a custom unsafe slash command subclass you need to register. + * + * @param commandObj UnsafeSlashCommand object to register as a subcommand. + */ +@UnsafeAPI +public fun SlashGroup.unsafeSubCommand( + commandObj: UnsafeSlashCommand +): UnsafeSlashCommand { + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { + throw InvalidCommandException( + commandObj.name, + "Groups may only contain up to $SUBCOMMAND_AND_GROUP_LIMIT commands." + ) + } + + try { + commandObj.validate() + subCommands.add(commandObj) + } catch (e: CommandRegistrationException) { + logger.error(e) { "Failed to register subcommand - $e" } + } catch (e: InvalidCommandException) { + logger.error(e) { "Failed to register subcommand - $e" } + } + + return commandObj +} + +/** + * DSL function for easily registering an unsafe subcommand, without arguments. + * + * Use this in your slash command function to register a subcommand that may be executed on Discord. + * + * @param body Builder lambda used for setting up the subcommand object. + */ +@UnsafeAPI +public suspend fun SlashGroup.unsafeSubCommand( + body: suspend UnsafeSlashCommand.() -> Unit +): UnsafeSlashCommand { + val commandObj = UnsafeSlashCommand(parent.extension, null, parent, this) + body(commandObj) + + return unsafeSubCommand(commandObj) +} + +// endregion diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt new file mode 100644 index 0000000000..bdcf94eeca --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt @@ -0,0 +1,31 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.types + +import com.kotlindiscord.kord.extensions.commands.application.message.InitialEphemeralMessageResponseBuilder +import com.kotlindiscord.kord.extensions.commands.application.message.InitialPublicMessageResponseBuilder +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI + +@UnsafeAPI +/** Sealed class representing the initial response types for an unsafe message command. **/ +public sealed class InitialMessageCommandResponse { + /** Respond with an ephemeral ack, without any content. **/ + public object EphemeralAck : InitialMessageCommandResponse() + + /** Respond with a public ack, without any content. **/ + public object PublicAck : InitialMessageCommandResponse() + + /** + * Respond with an ephemeral ack, including message content. + * + * @param builder Response builder, containing the message content. + */ + public data class EphemeralResponse(val builder: InitialEphemeralMessageResponseBuilder) : + InitialMessageCommandResponse() + + /** + * Respond with a public ack, including message content. + * + * @param builder Response builder, containing the message content. + **/ + public data class PublicResponse(val builder: InitialPublicMessageResponseBuilder) : + InitialMessageCommandResponse() +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt new file mode 100644 index 0000000000..14e0b985d3 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt @@ -0,0 +1,31 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.types + +import com.kotlindiscord.kord.extensions.commands.application.slash.InitialEphemeralSlashResponseBuilder +import com.kotlindiscord.kord.extensions.commands.application.slash.InitialPublicSlashResponseBehavior +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI + +@UnsafeAPI +/** Sealed class representing the initial response types for an unsafe slash command. **/ +public sealed class InitialSlashCommandResponse { + /** Respond with an ephemeral ack, without any content. **/ + public object EphemeralAck : InitialSlashCommandResponse() + + /** Respond with a public ack, without any content. **/ + public object PublicAck : InitialSlashCommandResponse() + + /** + * Respond with an ephemeral ack, including message content. + * + * @param builder Response builder, containing the message content. + */ + public data class EphemeralResponse(val builder: InitialEphemeralSlashResponseBuilder) : + InitialSlashCommandResponse() + + /** + * Respond with a public ack, including message content. + * + * @param builder Response builder, containing the message content. + **/ + public data class PublicResponse(val builder: InitialPublicSlashResponseBehavior) : + InitialSlashCommandResponse() +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt new file mode 100644 index 0000000000..9a3fe1df9d --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt @@ -0,0 +1,31 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.types + +import com.kotlindiscord.kord.extensions.commands.application.user.InitialEphemeralUserResponseBuilder +import com.kotlindiscord.kord.extensions.commands.application.user.InitialPublicUserResponseBuilder +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI + +@UnsafeAPI +/** Sealed class representing the initial response types for an unsafe user command. **/ +public sealed class InitialUserCommandResponse { + /** Respond with an ephemeral ack, without any content. **/ + public object EphemeralAck : InitialUserCommandResponse() + + /** Respond with a public ack, without any content. **/ + public object PublicAck : InitialUserCommandResponse() + + /** + * Respond with an ephemeral ack, including message content. + * + * @param builder Response builder, containing the message content. + */ + public data class EphemeralResponse(val builder: InitialEphemeralUserResponseBuilder) : + InitialUserCommandResponse() + + /** + * Respond with a public ack, including message content. + * + * @param builder Response builder, containing the message content. + **/ + public data class PublicResponse(val builder: InitialPublicUserResponseBuilder) : + InitialUserCommandResponse() +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt new file mode 100644 index 0000000000..f7d664b1b5 --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -0,0 +1,117 @@ +package com.kotlindiscord.kord.extensions.modules.unsafe.types + +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.pagination.BaseButtonPaginator +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator +import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import dev.kord.core.behavior.interaction.* +import dev.kord.core.entity.Message +import dev.kord.core.entity.interaction.EphemeralFollowupMessage +import dev.kord.core.entity.interaction.PublicFollowupMessage +import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder +import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder +import java.util.* + +/** Interface representing a generic, unsafe interaction action context. **/ +@UnsafeAPI +public interface UnsafeInteractionContext { + /** Response created by acknowledging the interaction. Generic. **/ + public val interactionResponse: InteractionResponseBehavior +} + +/** Respond to the current interaction with a public followup. **/ +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.respondPublic( + builder: PublicFollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage { + return when (val interaction = interactionResponse) { + is PublicInteractionResponseBehavior -> interaction.followUp(builder) + is EphemeralInteractionResponseBehavior -> interaction.followUpPublic(builder) + + else -> error("Unsupported initial interaction response type - please report this.") + } +} + +/** Respond to the current interaction with an ephemeral followup, or throw if it isn't ephemeral. **/ +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.respondEphemeral( + builder: EphemeralFollowupMessageCreateBuilder.() -> Unit +): EphemeralFollowupMessage { + val interaction = interactionResponse as? EphemeralInteractionResponseBehavior ?: error( + "Initial interaction response is not public." + ) + + return interaction.followUpEphemeral(builder) +} + +/** + * Edit the current interaction's response, or throw if it isn't public. + */ +@Suppress("UseIfInsteadOfWhen") +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.editPublic( + builder: PublicInteractionResponseModifyBuilder.() -> Unit +): Message { + return when (val interaction = interactionResponse) { + is PublicInteractionResponseBehavior -> interaction.edit(builder) + + else -> error("Initial interaction response was not public.") + } +} + +/** + * Edit the current interaction's response, or throw if it isn't ephemeral. + */ +@Suppress("UseIfInsteadOfWhen") +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.editEphemeral( + builder: EphemeralInteractionResponseModifyBuilder.() -> Unit +) { + when (val interaction = interactionResponse) { + is EphemeralInteractionResponseBehavior -> interaction.edit(builder) + + else -> error("Initial interaction response was not ephemeral.") + } +} + +/** Create a paginator that edits the original interaction. **/ +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.editingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): BaseButtonPaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return when (val interaction = interactionResponse) { + is PublicInteractionResponseBehavior -> PublicResponsePaginator(pages, interaction) + is EphemeralInteractionResponseBehavior -> EphemeralResponsePaginator(pages, interaction) + + else -> error("Unsupported initial interaction response type - please report this.") + } +} + +/** Create a paginator that creates a follow-up message, and edits that. **/ +@Suppress("UseIfInsteadOfWhen") +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.respondingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): BaseButtonPaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return when (val interaction = interactionResponse) { + is PublicInteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) + + else -> error("Initial interaction response was not public.") + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0e1e054b29..a8c88797d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,5 +38,6 @@ include("extra-modules:extra-mappings") include("modules:java-time") include("modules:time4j") +include("modules:unsafe") include("token-parser") From 8367409419e9b68881721643340610f21c27b528 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 15:51:27 +0100 Subject: [PATCH 032/131] Doc comments for initialResponse vars --- .../extensions/modules/unsafe/commands/UnsafeMessageCommand.kt | 2 +- .../extensions/modules/unsafe/commands/UnsafeSlashCommand.kt | 2 +- .../extensions/modules/unsafe/commands/UnsafeUserCommand.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt index 70bcd73560..babb0016e7 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -21,7 +21,7 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent public class UnsafeMessageCommand( extension: Extension ) : MessageCommand(extension) { - /** @suppress **/ + /** Initial response type. Change this to decide what happens when this message command action is executed. **/ public var initialResponse: InitialMessageCommandResponse = InitialMessageCommandResponse.EphemeralAck override suspend fun call(event: MessageCommandInteractionCreateEvent) { diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt index d5b9422074..1407c56077 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -29,7 +29,7 @@ public class UnsafeSlashCommand( public override val parentCommand: SlashCommand<*, *>? = null, public override val parentGroup: SlashGroup? = null ) : SlashCommand, A>(extension) { - /** @suppress **/ + /** Initial response type. Change this to decide what happens when this slash command is executed. **/ public var initialResponse: InitialSlashCommandResponse = InitialSlashCommandResponse.EphemeralAck override suspend fun call(event: ChatInputCommandInteractionCreateEvent) { diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt index 8c21381bb6..6eeb130317 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -21,7 +21,7 @@ import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent public class UnsafeUserCommand( extension: Extension ) : UserCommand(extension) { - /** @suppress **/ + /** Initial response type. Change this to decide what happens when this user command action is executed. **/ public var initialResponse: InitialUserCommandResponse = InitialUserCommandResponse.EphemeralAck override suspend fun call(event: UserCommandInteractionCreateEvent) { From 8d5dcb134cc703c957762f05400e6474630c378c Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 17:44:07 +0100 Subject: [PATCH 033/131] Fix command style disabled warnings --- .../com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 757e4eab3e..41ca0829d8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -140,8 +140,8 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva getKoin().get().initialRegistration() } else { logger.info { - "Slash command support is disabled - set `enabled` to `true` in the `slashCommands` builder" + - " if you want to use them." + "Application command support is disabled - set `enabled` to `true` in the " + + "`applicationCommands` builder if you want to use them." } } } @@ -161,6 +161,11 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva on { getKoin().get().handleEvent(this) } + } else { + logger.info { + "Chat command support is disabled - set `enabled` to `true` in the `chatCommands` builder" + + " if you want to use them." + } } if (settings.applicationCommandsBuilder.enabled) { From 3464427dc984c62559e2373d7f73d09b5aa7b0bf Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Sep 2021 21:27:05 +0100 Subject: [PATCH 034/131] Default guild and paginator fixes --- .../kord/extensions/commands/application/slash/SlashCommand.kt | 2 +- .../kord/extensions/pagination/BaseButtonPaginator.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 74446ed1f2..61fd139880 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -56,7 +56,7 @@ public abstract class SlashCommand, A : Arguments> override val type: ApplicationCommandType = ApplicationCommandType.ChatInput - override var guildId: Snowflake? = if (parentCommand != null && parentGroup != null) { + override var guildId: Snowflake? = if (parentCommand == null && parentGroup == null) { settings.applicationCommandsBuilder.defaultGuild } else { null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index a17f9a5fc6..b28716e373 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -83,8 +83,8 @@ public abstract class BaseButtonPaginator( } override suspend fun destroy() { - task?.cancel() runTimeoutCallbacks() + task?.cancel() } override suspend fun setup() { From 293ca1dd175c4dee617ba207c8fc575653aa42dd Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 9 Sep 2021 09:33:17 +0100 Subject: [PATCH 035/131] Component unknown interaction warn -> debug --- .../kord/extensions/components/ComponentRegistry.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt index 1c3f67a0c9..6b2ae4d206 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt @@ -39,7 +39,7 @@ public open class ComponentRegistry { when (val c = components[id]) { is InteractionButtonWithAction<*> -> c.call(event) - null -> logger.warn { "Button interaction received for unknown component ID: $id" } + null -> logger.debug { "Button interaction received for unknown component ID: $id" } else -> logger.warn { "Button interaction received for component ($id), but that component is not a button component with " + @@ -55,7 +55,7 @@ public open class ComponentRegistry { when (val c = components[id]) { is SelectMenu<*> -> c.call(event) - null -> logger.warn { "Select Menu interaction received for unknown component ID: $id" } + null -> logger.debug { "Select Menu interaction received for unknown component ID: $id" } else -> logger.warn { "Select Menu interaction received for component ($id), but that component is not a select menu" From 91b4c490231fd298c421d82330c0f933eea00f98 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 9 Sep 2021 09:38:43 +0100 Subject: [PATCH 036/131] Fix snowflake-based top channel checks --- .../kotlindiscord/kord/extensions/checks/TopChannelChecks.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt index 8a10a83364..4d4c88848d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt @@ -123,7 +123,7 @@ public suspend fun CheckContext.inTopChannel(id: Snowflake) { fail() } else { - inChannel { channel } + inTopChannel { channel } } } @@ -153,7 +153,7 @@ public suspend fun CheckContext.notInTopChannel(id: Snowflake) { pass() } else { - notInChannel { channel } + notInTopChannel { channel } } } From 0f9b0f4c22c597a0d278731c63cd59182ca53f38 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 9 Sep 2021 10:45:30 +0100 Subject: [PATCH 037/131] Top channel workaround --- .../com/kotlindiscord/kord/extensions/checks/CheckUtils.kt | 3 +-- .../kotlindiscord/kord/extensions/checks/TopChannelChecks.kt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 4a40b17057..06339f6f78 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -8,7 +8,6 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.* import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior -import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.entity.interaction.GuildApplicationCommandInteraction import dev.kord.core.event.Event import dev.kord.core.event.channel.* @@ -80,7 +79,7 @@ public suspend fun channelFor(event: Event): ChannelBehavior? { public suspend fun topChannelFor(event: Event): ChannelBehavior? { val channel = channelFor(event) ?: return null - return if (channel is ThreadChannel) { + return if (channel is ThreadChannelBehavior) { channel.parent } else { channel diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt index 4d4c88848d..11ce91282f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/TopChannelChecks.kt @@ -123,7 +123,7 @@ public suspend fun CheckContext.inTopChannel(id: Snowflake) { fail() } else { - inTopChannel { channel } + inTopChannel { channel.asChannel() } } } @@ -153,7 +153,7 @@ public suspend fun CheckContext.notInTopChannel(id: Snowflake) { pass() } else { - notInTopChannel { channel } + notInTopChannel { channel.asChannel() } } } From 2970cf1cd9e29d1c24791e57a1023fa8cfb799f6 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 9 Sep 2021 11:11:05 +0100 Subject: [PATCH 038/131] Update linkie for Hashed --- extra-modules/extra-mappings/README.md | 15 +- extra-modules/extra-mappings/build.gradle.kts | 10 ++ .../extra/mappings/MappingsExtension.kt | 140 ++++++++++++++++++ .../arguments/HashedMojangArguments.kt | 27 ++++ .../resources/kordex/mappings/default.toml | 2 +- .../kord/extensions/test/bot/Bot.kt | 20 +-- libs.versions.toml | 2 +- 7 files changed, 199 insertions(+), 17 deletions(-) create mode 100644 extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/HashedMojangArguments.kt diff --git a/extra-modules/extra-mappings/README.md b/extra-modules/extra-mappings/README.md index 3f7001e804..ec10bcf155 100644 --- a/extra-modules/extra-mappings/README.md +++ b/extra-modules/extra-mappings/README.md @@ -14,15 +14,19 @@ If you're looking for older versions (and older tags), you can find them * **Maven repo:** `https://maven.kotlindiscord.com/repository/maven-public/` * **Maven coordinates:** `com.kotlindiscord.kord.extensions:extra-mappings:VERSION` -* If Fabric dependencies fail to resolve, you may also need to add their Maven repo: `https://maven.fabricmc.net` + +* Some extra Maven repos are required: + * **FabricMC**: `https://maven.fabricmc.net/` + * **QuiltMC (Releases)**: `https://maven.quiltmc.org/repository/release/` + * **QuiltMC (Snapshots)**: `https://maven.quiltmc.org/repository/snapshot/` At its simplest, you can add this extension directly to your bot with no further configuration. For example: ```kotlin suspend fun main() { val bot = ExtensibleBot(System.getenv("TOKEN")) { - commands { - defaultPrefix = "!" + chatCommands { + enabled = true } extensions { @@ -42,8 +46,9 @@ they're detailed below. This extension provides a number of commands for use on Discord. -* Commands for retrieving information about mappings namespaces: `legacy-yarn`, `mcp`, `mojang`, `plasma`, `yarn` +* Commands for retrieving information about mappings namespaces: `hashed`, `legacy-yarn`, `mcp`, `mojang`, `plasma`, `yarn` and `yarrn` +* Hashed Mojang-specific lookup commands: `hc`, `hf` and `hm` * Legacy Yarn-specific lookup commands: `lyc`, `lyf` and `lym` * MCP-specific lookup commands: `mcpc`, `mcpf` and `mcpm` * Mojang-specific lookup commands: `mmc`, `mmf` and `mmm` @@ -80,7 +85,7 @@ following configuration keys are available: other guilds. This setting takes priority over `guilds.banned`. * `guilds.banned`: List of guilds mappings commands may **not** be run within. When set, mappings commands may not be run within the given guilds. -* `settings.namespaces`: List of enabled namespaces. Currently, `legacy-yarn`, `mcp`, `mojang`, `plasma`, `yarn` +* `settings.namespaces`: List of enabled namespaces. Currently, `hashed-mojang`, `legacy-yarn`, `mcp`, `mojang`, `plasma`, `yarn` and `yarrn` are supported, and they will all be enabled by default. * `settings.timeout`: Time (in seconds) to wait before destroying mappings paginators, defaulting to 5 minutes (300 seconds). Be careful when setting this value to something high - a busy bot may end up running out of memory if diff --git a/extra-modules/extra-mappings/build.gradle.kts b/extra-modules/extra-mappings/build.gradle.kts index 13bbbddc82..039016e5b6 100644 --- a/extra-modules/extra-mappings/build.gradle.kts +++ b/extra-modules/extra-mappings/build.gradle.kts @@ -18,6 +18,16 @@ repositories { url = uri("https://maven.fabricmc.net/") } + maven { + name = "QuiltMC (Releases)" + url = uri("https://maven.quiltmc.org/repository/release/") + } + + maven { + name = "QuiltMC (Snapshots)" + url = uri("https://maven.quiltmc.org/repository/snapshot/") + } + maven { name = "Shedaniel" url = uri("https://maven.shedaniel.me") diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index f7fabab576..4ff03c7d61 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -51,6 +51,7 @@ class MappingsExtension : Extension() { "legacy-yarn" -> namespaces.add(LegacyYarnNamespace) "mcp" -> namespaces.add(MCPNamespace) "mojang" -> namespaces.add(MojangNamespace) + "hashed-mojang" -> namespaces.add(MojangHashedNamespace) "plasma" -> namespaces.add(PlasmaNamespace) "yarn" -> namespaces.add(YarnNamespace) "yarrn" -> namespaces.add(YarrnNamespace) @@ -69,6 +70,7 @@ class MappingsExtension : Extension() { val legacyYarnEnabled = enabledNamespaces.contains("legacy-yarn") val mcpEnabled = enabledNamespaces.contains("mcp") val mojangEnabled = enabledNamespaces.contains("mojang") + val hashedMojangEnabled = enabledNamespaces.contains("hashed-mojang") val plasmaEnabled = enabledNamespaces.contains("plasma") val yarnEnabled = enabledNamespaces.contains("yarn") val yarrnEnabled = enabledNamespaces.contains("yarrn") @@ -274,6 +276,75 @@ class MappingsExtension : Extension() { // endregion + // region: Hashed Mojang mappings lookups + + if (hashedMojangEnabled) { + // Class + chatCommand(::HashedMojangArguments) { + name = "hc" + aliases = arrayOf("hmojc", "hmojmapc", "hmc", "qc") + + description = "Look up Hashed Mojang mappings info for a class.\n\n" + + + "**Channels:** " + Channels.values().joinToString(", ") { "`${it.str}`" } + + "\n\n" + + + "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + + "command." + + check { customChecks(name, MojangHashedNamespace) } + check(categoryCheck, channelCheck, guildCheck) // Default checks + + action { + queryClasses(MojangHashedNamespace, arguments.query, arguments.version, arguments.channel?.str) + } + } + + // Field + chatCommand(::HashedMojangArguments) { + name = "hf" + aliases = arrayOf("hmojf", "hmojmapf", "hmf", "qf") + + description = "Look up Hashed Mojang mappings info for a field.\n\n" + + + "**Channels:** " + Channels.values().joinToString(", ") { "`${it.str}`" } + + "\n\n" + + + "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + + "command." + + check { customChecks(name, MojangHashedNamespace) } + check(categoryCheck, channelCheck, guildCheck) // Default checks + + action { + queryFields(MojangHashedNamespace, arguments.query, arguments.version, arguments.channel?.str) + } + } + + // Method + chatCommand(::HashedMojangArguments) { + name = "hm" + aliases = arrayOf("hmojm", "hmojmapm", "hmm", "qm") + + description = "Look up Hashed Mojang mappings info for a method.\n\n" + + + "**Channels:** " + Channels.values().joinToString(", ") { "`${it.str}`" } + + "\n\n" + + + "For more information or a list of versions for Mojang mappings, you can use the `mojang` " + + "command." + + check { customChecks(name, MojangHashedNamespace) } + check(categoryCheck, channelCheck, guildCheck) // Default checks + + action { + queryMethods(MojangHashedNamespace, arguments.query, arguments.version, arguments.channel?.str) + } + } + } + + // endregion + // region: Plasma mappings lookups if (plasmaEnabled) { @@ -672,6 +743,75 @@ class MappingsExtension : Extension() { } } + if (hashedMojangEnabled) { + chatCommand { + name = "hashed" + aliases = arrayOf("hashed-mojmap", "hashed-mojang", "quilt") + + description = "Get information and a list of supported versions for hashed Mojang mappings." + + check { customChecks(name, MojangHashedNamespace) } + check(categoryCheck, channelCheck, guildCheck) // Default checks + + action { + val defaultVersion = MojangHashedNamespace.getDefaultVersion() + val allVersions = MojangHashedNamespace.getAllSortedVersions() + + val pages = allVersions.chunked(VERSION_CHUNK_SIZE).map { + it.joinToString("\n") { version -> + if (version == defaultVersion) { + "**» $version** (Default)" + } else { + "**»** $version" + } + } + }.toMutableList() + + pages.add( + 0, + "Hashed Mojang mappings are available for queries across **${allVersions.size}** " + + "versions.\n\n" + + + "**Default version:** $defaultVersion\n\n" + + + "**Channels:** " + Channels.values().joinToString(", ") { "`${it.str}`" } + + "\n" + + "**Commands:** `hc`, `hf`, `hm`\n\n" + + + "For a full list of supported hashed Mojang versions, please view the rest of the pages." + ) + + val pagesObj = Pages() + val pageTitle = "Mappings info: Hashed Mojang" + + pages.forEach { + pagesObj.addPage( + Page { + description = it + title = pageTitle + + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) + } + + val paginator = MessageButtonPaginator( + targetMessage = event.message, + pages = pagesObj, + keepEmbed = true, + owner = message.author, + timeoutSeconds = getTimeout(), + locale = getLocale(), + ) + + paginator.send() + } + } + } + if (plasmaEnabled) { chatCommand { name = "plasma" diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/HashedMojangArguments.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/HashedMojangArguments.kt new file mode 100644 index 0000000000..08e39f493a --- /dev/null +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/arguments/HashedMojangArguments.kt @@ -0,0 +1,27 @@ +package com.kotlindiscord.kord.extensions.modules.extra.mappings.arguments + +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalEnum +import com.kotlindiscord.kord.extensions.commands.converters.impl.string +import com.kotlindiscord.kord.extensions.modules.extra.mappings.converters.optionalMappingsVersion +import com.kotlindiscord.kord.extensions.modules.extra.mappings.enums.Channels +import me.shedaniel.linkie.namespaces.MojangHashedNamespace + +/** Arguments for hashed Mojang mappings lookup commands. **/ +@Suppress("UndocumentedPublicProperty") +class HashedMojangArguments : Arguments() { + val query by string("query", "Name to query mappings for") + + val channel by optionalEnum( + displayName = "channel", + description = "Mappings channel to use for this query", + typeName = "official/snapshot" + ) + + val version by optionalMappingsVersion( + "version", + "Minecraft version to use for this query", + true, + MojangHashedNamespace + ) +} diff --git a/extra-modules/extra-mappings/src/main/resources/kordex/mappings/default.toml b/extra-modules/extra-mappings/src/main/resources/kordex/mappings/default.toml index 787f875c64..b0e55dcc7e 100644 --- a/extra-modules/extra-mappings/src/main/resources/kordex/mappings/default.toml +++ b/extra-modules/extra-mappings/src/main/resources/kordex/mappings/default.toml @@ -20,7 +20,7 @@ allowed = [] banned = [] [settings] -# Which namespaces to allow lookups for - "legacy-yarn", "plasma", "mcp", "mojang", "yarn" or "yarrn" +# Which namespaces to allow lookups for - "hashed-mojang", "legacy-yarn", "plasma", "mcp", "mojang", "yarn" or "yarrn" namespaces = ["legacy-yarn", "plasma", "mcp", "mojang", "yarn", "yarrn"] # How long to wait before closing mappings paginators (in seconds), defaults to 5 minutes diff --git a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index cbde7e3c57..eaf24c33fe 100644 --- a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -4,7 +4,6 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.checks.isNotBot import com.kotlindiscord.kord.extensions.modules.extra.mappings.extMappings import com.kotlindiscord.kord.extensions.utils.env -import me.shedaniel.linkie.namespaces.YarnNamespace import org.koin.core.logger.Level suspend fun main() { @@ -13,6 +12,7 @@ suspend fun main() { chatCommands { check { isNotBot() } + enabled = true } applicationCommands { @@ -21,15 +21,15 @@ suspend fun main() { extensions { extMappings { - namespaceCheck { namespace -> - { - if (namespace == YarnNamespace) { - pass() - } else { - fail("Yarn only, ya dummy.") - } - } - } +// namespaceCheck { namespace -> +// { +// if (namespace == YarnNamespace) { +// pass() +// } else { +// fail("Yarn only, ya dummy.") +// } +// } +// } } } } diff --git a/libs.versions.toml b/libs.versions.toml index 159dd92a43..7d91766de2 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -15,7 +15,7 @@ kord = "0.8.0-M5" kotlinpoet = "1.8.0" ksp = "1.5.30-1.0.0-beta08" kx-ser = "1.2.2" -linkie = "1.0.85" +linkie = "1.0.88" logback = "1.2.3" logging = "2.0.6" sentry = "5.0.0" From 6db8d25c33f566df6636194c0c67ffda8ff2e2ac Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 9 Sep 2021 14:05:02 +0100 Subject: [PATCH 039/131] Remove command threadpool, mappings fixes --- .idea/jarRepositories.xml | 10 ++++++++++ .../modules/extra/mappings/MappingsExtension.kt | 8 ++++---- .../main/resources/kordex/mappings/default.toml | 2 +- .../extensions/builders/ExtensibleBotBuilder.kt | 3 --- .../commands/chat/ChatCommandRegistry.kt | 15 +-------------- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 3305aa1d69..8fc19501fa 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -61,5 +61,15 @@ ( else -> this } + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + emitEventAsync(EphemeralSlashCommandInvocationEvent(this, event)) + try { - if (!commandObj.runChecks(event)) { + if (!runChecks(event)) { + emitEventAsync( + EphemeralSlashCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondEphemeral { content = e.reason } + emitEventAsync( + EphemeralSlashCommandFailedChecksEvent( + this, + event, + e.reason + ) + ) + return } - commandObj.run(event) - } - - override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { val response = if (initialResponseBuilder != null) { event.interaction.respondEphemeral { initialResponseBuilder!!(event) } } else { @@ -82,19 +101,46 @@ public class EphemeralSlashCommand( try { checkBotPerms(context) + } catch (e: CommandException) { + respondText(context, e.reason) + + emitEventAsync( + EphemeralSlashCommandFailedChecksEvent( + this, + event, + e.reason + ) + ) - if (arguments != null) { + return + } + if (arguments != null) { + try { val args = registry.argumentParser.parse(arguments, context) context.populateArgs(args) + } catch (e: CommandException) { + respondText(context, e.reason) + emitEventAsync(EphemeralSlashCommandFailedParsingEvent(this, event, e.reason)) + + return } + } + try { body(context) - } catch (e: CommandException) { - respondText(context, e.reason) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(EphemeralSlashCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t, this) + + return } + + emitEventAsync(EphemeralSlashCommandSucceededEvent(this, event)) } override suspend fun respondText(context: EphemeralSlashCommandContext, message: String) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 9a49ff4718..e08deaa7f8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -4,6 +4,7 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.CommandException import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondPublic @@ -54,20 +55,32 @@ public class PublicSlashCommand( else -> this } + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + emitEventAsync(PublicSlashCommandInvocationEvent(this, event)) + try { - if (!commandObj.runChecks(event)) { + if (!runChecks(event)) { + emitEventAsync( + PublicSlashCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondPublic { content = e.reason } + emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) + return } - commandObj.run(event) - } - - override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { val response = if (initialResponseBuilder != null) { event.interaction.respondPublic { initialResponseBuilder!!(event) } } else { @@ -82,19 +95,39 @@ public class PublicSlashCommand( try { checkBotPerms(context) + } catch (e: CommandException) { + respondText(context, e.reason) + emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) - if (arguments != null) { + return + } + if (arguments != null) { + try { val args = registry.argumentParser.parse(arguments, context) context.populateArgs(args) + } catch (e: CommandException) { + respondText(context, e.reason) + emitEventAsync(PublicSlashCommandFailedParsingEvent(this, event, e.reason)) + + return } + } + try { body(context) - } catch (e: CommandException) { - respondText(context, e.reason) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(PublicSlashCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t, this) + + return } + + emitEventAsync(PublicSlashCommandSucceededEvent(this, event)) } override suspend fun respondText(context: PublicSlashCommandContext, message: String) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 6de75978d3..54707e707e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -3,6 +3,10 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFailedChecksEvent +import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFailedWithExceptionEvent +import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandInvocationEvent +import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondEphemeral @@ -25,13 +29,25 @@ public class EphemeralUserCommand( } override suspend fun call(event: UserCommandInteractionCreateEvent) { + emitEventAsync(EphemeralUserCommandInvocationEvent(this, event)) + try { if (!runChecks(event)) { + emitEventAsync( + EphemeralUserCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondEphemeral { content = e.reason } + emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) + return } @@ -49,12 +65,25 @@ public class EphemeralUserCommand( try { checkBotPerms(context) - body(context) } catch (e: CommandException) { respondText(context, e.reason) + emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) + + return + } + + try { + body(context) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(EphemeralUserCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) } + + emitEventAsync(EphemeralUserCommandSucceededEvent(this, event)) } override suspend fun respondText(context: EphemeralUserCommandContext, message: String) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 41662f4fa6..31e9d7319c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -3,6 +3,10 @@ package com.kotlindiscord.kord.extensions.commands.application.user import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailedChecksEvent +import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailedWithExceptionEvent +import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandInvocationEvent +import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond import dev.kord.core.behavior.interaction.respondPublic @@ -25,13 +29,25 @@ public class PublicUserCommand( } override suspend fun call(event: UserCommandInteractionCreateEvent) { + emitEventAsync(PublicUserCommandInvocationEvent(this, event)) + try { if (!runChecks(event)) { + emitEventAsync( + PublicUserCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondPublic { content = e.reason } + emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) + return } @@ -49,12 +65,25 @@ public class PublicUserCommand( try { checkBotPerms(context) - body(context) } catch (e: CommandException) { respondText(context, e.reason) + emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) + + return + } + + try { + body(context) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(PublicUserCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) } + + emitEventAsync(PublicUserCommandSucceededEvent(this, event)) } override suspend fun respondText(context: PublicUserCommandContext, message: String) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 3a12533d1a..1dfad8defd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -1,4 +1,4 @@ -@file:Suppress("StringLiteralDuplication") +@file:Suppress("StringLiteralDuplication", "TooGenericExceptionCaught") package com.kotlindiscord.kord.extensions.commands.chat @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.Command +import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.EMPTY_VALUE_STRING import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider @@ -323,6 +324,29 @@ public open class ChatCommand( return true } + /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ + @Throws(CommandException::class) + public open suspend fun checkBotPerms(context: ChatCommandContext) { + if (context.guild != null) { + val perms = (context.channel.asChannel() as GuildChannel) + .permissionsForMember(kord.selfId) + + val missingPerms = requiredPerms.filter { !perms.contains(it) } + + if (missingPerms.isNotEmpty()) { + throw CommandException( + context.translate( + "commands.error.missingBotPermissions", + null, + replacements = arrayOf( + missingPerms.map { it.translate(context) }.joinToString(", ") + ) + ) + ) + } + } + } + /** * Execute this command, given a [MessageCreateEvent]. * @@ -346,7 +370,24 @@ public open class ChatCommand( argString: String, skipChecks: Boolean = false ) { - if (!skipChecks && !runChecks(event)) { + emitEventAsync(ChatCommandInvocationEvent(this, event)) + + try { + if (!skipChecks && !runChecks(event)) { + emitEventAsync( + ChatCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + + return + } + } catch (e: CommandException) { + emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) + event.message.respond(e.reason) + return } @@ -380,36 +421,36 @@ public open class ChatCommand( } } - @Suppress("TooGenericExceptionCaught") // Anything could happen here try { - if (context.guild != null) { - val perms = (context.channel.asChannel() as GuildChannel) - .permissionsForMember(kord.selfId) - - val missingPerms = requiredPerms.filter { !perms.contains(it) } + checkBotPerms(context) + } catch (e: CommandException) { + event.message.respond(e.reason) + emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) - if (missingPerms.isNotEmpty()) { - throw CommandException( - context.translate( - "commands.error.missingBotPermissions", - null, - replacements = arrayOf( - missingPerms.map { it.translate(context) }.joinToString(", ") - ) - ) - ) - } - } + return + } - if (this.arguments != null) { + if (this.arguments != null) { + try { val parsedArgs = registry.parser.parse(this.arguments!!, context) context.populateArgs(parsedArgs) + } catch (e: CommandException) { + event.message.respond(e.reason) + emitEventAsync(ChatCommandFailedParsingEvent(this, event, e.reason)) + + return } + } + try { this.body(context) - } catch (e: CommandException) { - event.message.respond(e.toString()) } catch (t: Throwable) { + if (t is CommandException) { + event.message.respond(t.reason) + } + + emitEventAsync(ChatCommandFailedWithExceptionEvent(this, event, t)) + if (sentry.enabled) { logger.trace { "Submitting error to sentry." } @@ -470,6 +511,10 @@ public open class ChatCommand( context.translate("commands.error.user", null) ) } + + return } + + emitEventAsync(ChatCommandSucceededEvent(this, event)) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt new file mode 100644 index 0000000000..9522467733 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt @@ -0,0 +1,37 @@ +package com.kotlindiscord.kord.extensions.commands.events + +import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand +import dev.kord.core.event.message.MessageCreateEvent + +/** Event emitted when a chat command is invoked. **/ +public data class ChatCommandInvocationEvent( + override val command: ChatCommand<*>, + override val event: MessageCreateEvent +) : CommandInvocationEvent, MessageCreateEvent> + +/** Event emitted when a chat command invocation succeeds. **/ +public data class ChatCommandSucceededEvent( + override val command: ChatCommand<*>, + override val event: MessageCreateEvent +) : CommandSucceededEvent, MessageCreateEvent> + +/** Event emitted when a chat command's checks fail. **/ +public data class ChatCommandFailedChecksEvent( + override val command: ChatCommand<*>, + override val event: MessageCreateEvent, + override val reason: String, +) : CommandFailedChecksEvent, MessageCreateEvent> + +/** Event emitted when a chat command's argument parsing fails. **/ +public data class ChatCommandFailedParsingEvent( + override val command: ChatCommand<*>, + override val event: MessageCreateEvent, + override val reason: String, +) : CommandFailedParsingEvent, MessageCreateEvent> + +/** Event emitted when a chat command's invocation fails with an exception. **/ +public data class ChatCommandFailedWithExceptionEvent( + override val command: ChatCommand<*>, + override val event: MessageCreateEvent, + override val throwable: Throwable +) : CommandFailedWithExceptionEvent, MessageCreateEvent> diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt new file mode 100644 index 0000000000..a9cd586057 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt @@ -0,0 +1,46 @@ +package com.kotlindiscord.kord.extensions.commands.events + +import com.kotlindiscord.kord.extensions.commands.Command +import com.kotlindiscord.kord.extensions.events.KordExEvent +import dev.kord.core.event.Event + +/** + * Sealed interface representing a basic command event. + * + * @param C Command type + * @param E Event type + */ +public sealed interface CommandEvent : KordExEvent { + /** Command object that this event concerns. **/ + public val command: C + + /** Event object that triggered this invocation. **/ + public val event: E +} + +/** Basic event emitted when a command is invoked. **/ +public sealed interface CommandInvocationEvent : CommandEvent + +/** Basic event emitted when a command's invocation succeeds. **/ +public sealed interface CommandSucceededEvent : CommandEvent + +/** Basic event emitted when a command's invocation fails, for one reason or another. **/ +public sealed interface CommandFailedEvent : CommandEvent + +/** Basic event emitted when a command's checks fail, including for required bot permissions. **/ +public sealed interface CommandFailedChecksEvent : CommandFailedEvent { + /** Human-readable failure reason. **/ + public val reason: String +} + +/** Basic event emitted when a command's argument parsing fails. **/ +public sealed interface CommandFailedParsingEvent : CommandFailedEvent { + /** Human-readable failure reason. **/ + public val reason: String +} + +/** Basic event emitted when a command's body invocation fails with an exception. **/ +public sealed interface CommandFailedWithExceptionEvent : CommandFailedEvent { + /** Exception thrown for this failure. **/ + public val throwable: Throwable +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/MessageCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/MessageCommandEvents.kt new file mode 100644 index 0000000000..06c996b023 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/MessageCommandEvents.kt @@ -0,0 +1,90 @@ +package com.kotlindiscord.kord.extensions.commands.events + +import com.kotlindiscord.kord.extensions.commands.application.message.EphemeralMessageCommand +import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand +import com.kotlindiscord.kord.extensions.commands.application.message.PublicMessageCommand +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent + +// region Invocation events + +/** Basic event emitted when a message command is invoked. **/ +public interface MessageCommandInvocationEvent> : + CommandInvocationEvent + +/** Event emitted when an ephemeral message command is invoked. **/ +public data class EphemeralMessageCommandInvocationEvent( + override val command: EphemeralMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandInvocationEvent + +/** Event emitted when a public message command is invoked. **/ +public data class PublicMessageCommandInvocationEvent( + override val command: PublicMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandInvocationEvent + +// endregion + +// region Succeeded events + +/** Basic event emitted when a message command invocation succeeds. **/ +public interface MessageCommandSucceededEvent> : + CommandSucceededEvent + +/** Event emitted when an ephemeral message command invocation succeeds. **/ +public data class EphemeralMessageCommandSucceededEvent( + override val command: EphemeralMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandSucceededEvent + +/** Event emitted when a public message command invocation succeeds. **/ +public data class PublicMessageCommandSucceededEvent( + override val command: PublicMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandSucceededEvent + +// endregion + +// region Failed events + +/** Basic event emitted when a message command invocation fails. **/ +public sealed interface MessageCommandFailedEvent> : + CommandFailedEvent + +/** Basic event emitted when a message command's checks fail. **/ +public interface MessageCommandFailedChecksEvent> : + MessageCommandFailedEvent, CommandFailedChecksEvent + +/** Event emitted when an ephemeral message command's checks fail. **/ +public data class EphemeralMessageCommandFailedChecksEvent( + override val command: EphemeralMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val reason: String +) : MessageCommandFailedChecksEvent + +/** Event emitted when a public message command's checks fail. **/ +public data class PublicMessageCommandFailedChecksEvent( + override val command: PublicMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val reason: String +) : MessageCommandFailedChecksEvent + +/** Basic event emitted when a message command invocation fails with an exception. **/ +public interface MessageCommandFailedWithExceptionEvent> : + MessageCommandFailedEvent, CommandFailedWithExceptionEvent + +/** Event emitted when an ephemeral message command invocation fails with an exception. **/ +public data class EphemeralMessageCommandFailedWithExceptionEvent( + override val command: EphemeralMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val throwable: Throwable +) : MessageCommandFailedWithExceptionEvent + +/** Event emitted when a public message command invocation fails with an exception. **/ +public data class PublicMessageCommandFailedWithExceptionEvent( + override val command: PublicMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val throwable: Throwable +) : MessageCommandFailedWithExceptionEvent + +// endregion diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt new file mode 100644 index 0000000000..a0817cdca0 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt @@ -0,0 +1,108 @@ +package com.kotlindiscord.kord.extensions.commands.events + +import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.PublicSlashCommand +import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent + +// region Invocation events + +/** Basic event emitted when a slash command is invoked. **/ +public interface SlashCommandInvocationEvent> : + CommandInvocationEvent + +/** Event emitted when an ephemeral slash command is invoked. **/ +public data class EphemeralSlashCommandInvocationEvent( + override val command: EphemeralSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandInvocationEvent> + +/** Event emitted when a public slash command is invoked. **/ +public data class PublicSlashCommandInvocationEvent( + override val command: PublicSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandInvocationEvent> + +// endregion + +// region Succeeded events + +/** Basic event emitted when a slash command invocation succeeds. **/ +public interface SlashCommandSucceededEvent> : + CommandSucceededEvent + +/** Event emitted when an ephemeral slash command invocation succeeds. **/ +public data class EphemeralSlashCommandSucceededEvent( + override val command: EphemeralSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandSucceededEvent> + +/** Event emitted when a public slash command invocation succeeds. **/ +public data class PublicSlashCommandSucceededEvent( + override val command: PublicSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandSucceededEvent> + +// endregion + +// region Failed events + +/** Basic event emitted when a slash command invocation fails. **/ +public sealed interface SlashCommandFailedEvent> : + CommandFailedEvent + +/** Basic event emitted when a slash command's checks fail. **/ +public interface SlashCommandFailedChecksEvent> : + SlashCommandFailedEvent, CommandFailedChecksEvent + +/** Event emitted when an ephemeral slash command's checks fail. **/ +public data class EphemeralSlashCommandFailedChecksEvent( + override val command: EphemeralSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String, +) : SlashCommandFailedChecksEvent> + +/** Event emitted when a public slash command's checks fail. **/ +public data class PublicSlashCommandFailedChecksEvent( + override val command: PublicSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String, +) : SlashCommandFailedChecksEvent> + +/** Basic event emitted when a slash command's argument parsing fails'. **/ +public interface SlashCommandFailedParsingEvent> : + SlashCommandFailedEvent, CommandFailedParsingEvent + +/** Event emitted when an ephemeral slash command's argument parsing fails'. **/ +public data class EphemeralSlashCommandFailedParsingEvent( + override val command: EphemeralSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String, +) : SlashCommandFailedParsingEvent> + +/** Event emitted when a public slash command's argument parsing fails'. **/ +public data class PublicSlashCommandFailedParsingEvent( + override val command: PublicSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String, +) : SlashCommandFailedParsingEvent> + +/** Basic event emitted when a slash command invocation fails with an exception. **/ +public interface SlashCommandFailedWithExceptionEvent> : + SlashCommandFailedEvent, CommandFailedWithExceptionEvent + +/** Event emitted when an ephemeral slash command invocation fails with an exception. **/ +public data class EphemeralSlashCommandFailedWithExceptionEvent( + override val command: EphemeralSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val throwable: Throwable +) : SlashCommandFailedWithExceptionEvent> + +/** Event emitted when a public slash command invocation fails with an exception. **/ +public data class PublicSlashCommandFailedWithExceptionEvent( + override val command: PublicSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val throwable: Throwable +) : SlashCommandFailedWithExceptionEvent> + +// endregion diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/UserCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/UserCommandEvents.kt new file mode 100644 index 0000000000..4c3b4ca027 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/UserCommandEvents.kt @@ -0,0 +1,90 @@ +package com.kotlindiscord.kord.extensions.commands.events + +import com.kotlindiscord.kord.extensions.commands.application.user.EphemeralUserCommand +import com.kotlindiscord.kord.extensions.commands.application.user.PublicUserCommand +import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +// region Invocation events + +/** Basic event emitted when a user command is invoked. **/ +public interface UserCommandInvocationEvent> : + CommandInvocationEvent + +/** Event emitted when an ephemeral user command is invoked. **/ +public data class EphemeralUserCommandInvocationEvent( + override val command: EphemeralUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandInvocationEvent + +/** Event emitted when a public user command is invoked. **/ +public data class PublicUserCommandInvocationEvent( + override val command: PublicUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandInvocationEvent + +// endregion + +// region Succeeded events + +/** Basic event emitted when a user command invocation succeeds. **/ +public interface UserCommandSucceededEvent> : + CommandSucceededEvent + +/** Event emitted when an ephemeral user command invocation succeeds. **/ +public data class EphemeralUserCommandSucceededEvent( + override val command: EphemeralUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandSucceededEvent + +/** Event emitted when a public user command invocation succeeds. **/ +public data class PublicUserCommandSucceededEvent( + override val command: PublicUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandSucceededEvent + +// endregion + +// region Failed events + +/** Basic event emitted when a user command invocation fails. **/ +public sealed interface UserCommandFailedEvent> : + CommandFailedEvent + +/** Basic event emitted when a user command's checks fail. **/ +public interface UserCommandFailedChecksEvent> : + UserCommandFailedEvent, CommandFailedChecksEvent + +/** Event emitted when an ephemeral user command's checks fail. **/ +public data class EphemeralUserCommandFailedChecksEvent( + override val command: EphemeralUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val reason: String, +) : UserCommandFailedChecksEvent + +/** Event emitted when a public user command's checks fail. **/ +public data class PublicUserCommandFailedChecksEvent( + override val command: PublicUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val reason: String, +) : UserCommandFailedChecksEvent + +/** Basic event emitted when a user command invocation fails with an exception. **/ +public interface UserCommandFailedWithExceptionEvent> : + UserCommandFailedEvent, CommandFailedWithExceptionEvent + +/** Event emitted when an ephemeral user command invocation fails with an exception. **/ +public data class EphemeralUserCommandFailedWithExceptionEvent( + override val command: EphemeralUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val throwable: Throwable +) : UserCommandFailedWithExceptionEvent + +/** Event emitted when a public user command invocation fails with an exception. **/ +public data class PublicUserCommandFailedWithExceptionEvent( + override val command: PublicUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val throwable: Throwable +) : UserCommandFailedWithExceptionEvent + +// endregion diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionEvent.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionEvent.kt deleted file mode 100644 index 0799f366bd..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.kotlindiscord.kord.extensions.events - -import com.kotlindiscord.kord.extensions.ExtensibleBot -import dev.kord.core.Kord -import dev.kord.core.event.Event -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/** - * Base interface for events fired by Kord Extensions. - */ -public abstract class ExtensionEvent : Event, KoinComponent { - /** Current bot instance for this event. **/ - public val bot: ExtensibleBot by inject() - - override val kord: Kord by inject() - override val shard: Int = -1 -} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionStateEvent.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionStateEvent.kt index 1811ea6451..a0f78fd447 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionStateEvent.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/ExtensionStateEvent.kt @@ -9,7 +9,7 @@ import com.kotlindiscord.kord.extensions.extensions.ExtensionState * @property extension Extension that has a state change * @property state Extension's new state */ -public class ExtensionStateEvent( +public data class ExtensionStateEvent( public val extension: Extension, public val state: ExtensionState -) : ExtensionEvent() +) : KordExEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt new file mode 100644 index 0000000000..a224944b5d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt @@ -0,0 +1,13 @@ +package com.kotlindiscord.kord.extensions.events + +import dev.kord.core.Kord +import dev.kord.core.event.Event +import org.koin.core.component.KoinComponent + +/** + * Base interface for events fired by Kord Extensions. + */ +public interface KordExEvent : Event, KoinComponent { + override val kord: Kord get() = getKoin().get() + override val shard: Int get() = -1 +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt new file mode 100644 index 0000000000..2fb81aaf3b --- /dev/null +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt @@ -0,0 +1,106 @@ +@file:OptIn(UnsafeAPI::class) + +package com.kotlindiscord.kord.extensions.modules.unsafe.commands + +import com.kotlindiscord.kord.extensions.commands.events.* +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent + +// region Message commands + +/** Event emitted when an unsafe message command is invoked. **/ +public data class UnsafeMessageCommandInvocationEvent( + override val command: UnsafeMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandInvocationEvent + +/** Event emitted when an unsafe message command invocation succeeds. **/ +public data class UnsafeMessageCommandSucceededEvent( + override val command: UnsafeMessageCommand, + override val event: MessageCommandInteractionCreateEvent +) : MessageCommandSucceededEvent + +/** Event emitted when an unsafe message command's checks fail. **/ +public data class UnsafeMessageCommandFailedChecksEvent( + override val command: UnsafeMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val reason: String +) : MessageCommandFailedChecksEvent + +/** Event emitted when an unsafe message command invocation fails with an exception. **/ +public data class UnsafeMessageCommandFailedWithExceptionEvent( + override val command: UnsafeMessageCommand, + override val event: MessageCommandInteractionCreateEvent, + override val throwable: Throwable +) : MessageCommandFailedWithExceptionEvent + +// endregion + +// region Slash commands + +/** Event emitted when an unsafe slash command is invoked. **/ +public data class UnsafeSlashCommandInvocationEvent( + override val command: UnsafeSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandInvocationEvent> + +/** Event emitted when an unsafe slash command invocation succeeds. **/ +public data class UnsafeSlashCommandSucceededEvent( + override val command: UnsafeSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent +) : SlashCommandSucceededEvent> + +/** Event emitted when an unsafe slash command's checks fail. **/ +public data class UnsafeSlashCommandFailedChecksEvent( + override val command: UnsafeSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String +) : SlashCommandFailedChecksEvent> + +/** Event emitted when an unsafe slash command's argument parsing fails. **/ +public data class UnsafeSlashCommandFailedParsingEvent( + override val command: UnsafeSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val reason: String +) : SlashCommandFailedParsingEvent> + +/** Event emitted when an unsafe slash command invocation fails with an exception. **/ +public data class UnsafeSlashCommandFailedWithExceptionEvent( + override val command: UnsafeSlashCommand<*>, + override val event: ChatInputCommandInteractionCreateEvent, + override val throwable: Throwable +) : SlashCommandFailedWithExceptionEvent> + +// endregion + +// region User commands + +/** Event emitted when an unsafe user command is invoked. **/ +public data class UnsafeUserCommandInvocationEvent( + override val command: UnsafeUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandInvocationEvent + +/** Event emitted when an unsafe user command invocation succeeds. **/ +public data class UnsafeUserCommandSucceededEvent( + override val command: UnsafeUserCommand, + override val event: UserCommandInteractionCreateEvent +) : UserCommandSucceededEvent + +/** Event emitted when an unsafe user command's checks fail. **/ +public data class UnsafeUserCommandFailedChecksEvent( + override val command: UnsafeUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val reason: String +) : UserCommandFailedChecksEvent + +/** Event emitted when an unsafe user command invocation fails with an exception. **/ +public data class UnsafeUserCommandFailedWithExceptionEvent( + override val command: UnsafeUserCommand, + override val event: UserCommandInteractionCreateEvent, + override val throwable: Throwable +) : UserCommandFailedWithExceptionEvent + +// endregion diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt index babb0016e7..6d0f223f9d 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -25,13 +25,24 @@ public class UnsafeMessageCommand( public var initialResponse: InitialMessageCommandResponse = InitialMessageCommandResponse.EphemeralAck override suspend fun call(event: MessageCommandInteractionCreateEvent) { + emitEventAsync(UnsafeMessageCommandInvocationEvent(this, event)) + try { if (!runChecks(event)) { + emitEventAsync( + UnsafeMessageCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) return } } catch (e: CommandException) { event.interaction.respondPublic { content = e.reason } + emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) + return } @@ -56,12 +67,28 @@ public class UnsafeMessageCommand( try { checkBotPerms(context) - body(context) } catch (e: CommandException) { respondText(context, e.reason) + emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) + + return + } + + try { + checkBotPerms(context) + body(context) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(UnsafeMessageCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) + + return } + + emitEventAsync(UnsafeMessageCommandSucceededEvent(this, event)) } override suspend fun respondText(context: UnsafeMessageCommandContext, message: String) { diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt index 1407c56077..c92d9d2d4e 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -55,20 +55,32 @@ public class UnsafeSlashCommand( else -> this } + commandObj.run(event) + } + + override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { + emitEventAsync(UnsafeSlashCommandInvocationEvent(this, event)) + try { - if (!commandObj.runChecks(event)) { + if (!runChecks(event)) { + emitEventAsync( + UnsafeSlashCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondPublic { content = e.reason } + emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) + return } - commandObj.run(event) - } - - override suspend fun run(event: ChatInputCommandInteractionCreateEvent) { val response = when (val r = initialResponse) { is InitialSlashCommandResponse.EphemeralAck -> event.interaction.acknowledgeEphemeral() is InitialSlashCommandResponse.PublicAck -> event.interaction.acknowledgePublic() @@ -90,19 +102,40 @@ public class UnsafeSlashCommand( try { checkBotPerms(context) + } catch (e: CommandException) { + respondText(context, e.reason) + emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) + + return + } + try { if (arguments != null) { val args = registry.argumentParser.parse(arguments, context) context.populateArgs(args) } - - body(context) } catch (e: CommandException) { respondText(context, e.reason) + emitEventAsync(UnsafeSlashCommandFailedParsingEvent(this, event, e.reason)) + + return + } + + try { + body(context) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(UnsafeSlashCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t, this) + + return } + + emitEventAsync(UnsafeSlashCommandSucceededEvent(this, event)) } override suspend fun respondText(context: UnsafeSlashCommandContext, message: String) { diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt index 6eeb130317..bc5d7dc9ec 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -25,13 +25,25 @@ public class UnsafeUserCommand( public var initialResponse: InitialUserCommandResponse = InitialUserCommandResponse.EphemeralAck override suspend fun call(event: UserCommandInteractionCreateEvent) { + emitEventAsync(UnsafeUserCommandInvocationEvent(this, event)) + try { if (!runChecks(event)) { + emitEventAsync( + UnsafeUserCommandFailedChecksEvent( + this, + event, + "Checks failed without a message." + ) + ) + return } } catch (e: CommandException) { event.interaction.respondPublic { content = e.reason } + emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) + return } @@ -56,12 +68,27 @@ public class UnsafeUserCommand( try { checkBotPerms(context) - body(context) } catch (e: CommandException) { respondText(context, e.reason) + emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) + + return + } + + try { + body(context) } catch (t: Throwable) { + if (t is CommandException) { + respondText(context, t.reason) + } + + emitEventAsync(UnsafeUserCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) + + return } + + emitEventAsync(UnsafeUserCommandSucceededEvent(this, event)) } override suspend fun respondText(context: UnsafeUserCommandContext, message: String) { From 22363159c4aacf94da46419b0794ffda760b0283 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 11 Sep 2021 21:45:18 +0100 Subject: [PATCH 048/131] Clean up interactions, allow "none" interaction for unsafe commands --- .../EphemeralInteractionContext.kt | 10 --- .../unsafe/commands/UnsafeMessageCommand.kt | 2 + .../unsafe/commands/UnsafeSlashCommand.kt | 2 + .../unsafe/commands/UnsafeUserCommand.kt | 2 + .../contexts/UnsafeMessageCommandContext.kt | 2 +- .../contexts/UnsafeSlashCommandContext.kt | 2 +- .../contexts/UnsafeUserCommandContext.kt | 3 +- .../types/InitialMessageCommandResponse.kt | 5 +- .../types/InitialSlashCommandResponse.kt | 5 +- .../types/InitialUserCommandResponse.kt | 5 +- .../unsafe/types/UnsafeInteractionContext.kt | 77 +++++++++++++++---- 11 files changed, 85 insertions(+), 30 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt index 924bea1936..e3f91dfd44 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/EphemeralInteractionContext.kt @@ -5,11 +5,8 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUpEphemeral -import dev.kord.core.behavior.interaction.followUpPublic import dev.kord.core.entity.interaction.EphemeralFollowupMessage -import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder import java.util.* @@ -28,13 +25,6 @@ public suspend inline fun EphemeralInteractionContext.respond( builder: EphemeralFollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) -/** - * Respond to the current interaction with a public followup. - */ -public suspend inline fun EphemeralInteractionContext.respondPublic( - builder: PublicFollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage = interactionResponse.followUpPublic(builder) - /** * Edit the current interaction's response. */ diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt index 6d0f223f9d..8e6151dfef 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -57,6 +57,8 @@ public class UnsafeMessageCommand( is InitialMessageCommandResponse.PublicResponse -> event.interaction.respondPublic { r.builder!!(event) } + + is InitialMessageCommandResponse.None -> null } val context = UnsafeMessageCommandContext(event, this, response) diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt index c92d9d2d4e..7f00fb9323 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -92,6 +92,8 @@ public class UnsafeSlashCommand( is InitialSlashCommandResponse.PublicResponse -> event.interaction.respondPublic { r.builder!!(event) } + + is InitialSlashCommandResponse.None -> null } val context = UnsafeSlashCommandContext(event, this, response) diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt index bc5d7dc9ec..f33773d827 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -58,6 +58,8 @@ public class UnsafeUserCommand( is InitialUserCommandResponse.PublicResponse -> event.interaction.respondPublic { r.builder!!(event) } + + is InitialUserCommandResponse.None -> null } val context = UnsafeUserCommandContext(event, this, response) diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt index 2d7e54569d..a05530c7bc 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeMessageCommandContext.kt @@ -12,5 +12,5 @@ import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent public class UnsafeMessageCommandContext( override val event: MessageCommandInteractionCreateEvent, override val command: MessageCommand, - override val interactionResponse: InteractionResponseBehavior, + override var interactionResponse: InteractionResponseBehavior?, ) : MessageCommandContext(event, command), UnsafeInteractionContext diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt index a87c42bdae..c34e2321e8 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeSlashCommandContext.kt @@ -13,5 +13,5 @@ import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent public class UnsafeSlashCommandContext( override val event: ChatInputCommandInteractionCreateEvent, override val command: UnsafeSlashCommand, - override val interactionResponse: InteractionResponseBehavior + override var interactionResponse: InteractionResponseBehavior? ) : SlashCommandContext, A>(event, command), UnsafeInteractionContext diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt index 6a22a5288f..02707a9740 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/contexts/UnsafeUserCommandContext.kt @@ -12,6 +12,5 @@ import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent public class UnsafeUserCommandContext( override val event: UserCommandInteractionCreateEvent, override val command: UserCommand, - override val interactionResponse: InteractionResponseBehavior - + override var interactionResponse: InteractionResponseBehavior? ) : UserCommandContext(event, command), UnsafeInteractionContext diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt index bdcf94eeca..9b03559863 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialMessageCommandResponse.kt @@ -4,8 +4,8 @@ import com.kotlindiscord.kord.extensions.commands.application.message.InitialEph import com.kotlindiscord.kord.extensions.commands.application.message.InitialPublicMessageResponseBuilder import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -@UnsafeAPI /** Sealed class representing the initial response types for an unsafe message command. **/ +@UnsafeAPI public sealed class InitialMessageCommandResponse { /** Respond with an ephemeral ack, without any content. **/ public object EphemeralAck : InitialMessageCommandResponse() @@ -13,6 +13,9 @@ public sealed class InitialMessageCommandResponse { /** Respond with a public ack, without any content. **/ public object PublicAck : InitialMessageCommandResponse() + /** Don't respond. Warning: You may not be able to respond in time! **/ + public object None : InitialMessageCommandResponse() + /** * Respond with an ephemeral ack, including message content. * diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt index 14e0b985d3..21ae7243a3 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialSlashCommandResponse.kt @@ -4,8 +4,8 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.InitialEphem import com.kotlindiscord.kord.extensions.commands.application.slash.InitialPublicSlashResponseBehavior import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -@UnsafeAPI /** Sealed class representing the initial response types for an unsafe slash command. **/ +@UnsafeAPI public sealed class InitialSlashCommandResponse { /** Respond with an ephemeral ack, without any content. **/ public object EphemeralAck : InitialSlashCommandResponse() @@ -13,6 +13,9 @@ public sealed class InitialSlashCommandResponse { /** Respond with a public ack, without any content. **/ public object PublicAck : InitialSlashCommandResponse() + /** Don't respond. Warning: You may not be able to respond in time! **/ + public object None : InitialSlashCommandResponse() + /** * Respond with an ephemeral ack, including message content. * diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt index 9a3fe1df9d..bef7c39269 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/InitialUserCommandResponse.kt @@ -4,8 +4,8 @@ import com.kotlindiscord.kord.extensions.commands.application.user.InitialEpheme import com.kotlindiscord.kord.extensions.commands.application.user.InitialPublicUserResponseBuilder import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -@UnsafeAPI /** Sealed class representing the initial response types for an unsafe user command. **/ +@UnsafeAPI public sealed class InitialUserCommandResponse { /** Respond with an ephemeral ack, without any content. **/ public object EphemeralAck : InitialUserCommandResponse() @@ -13,6 +13,9 @@ public sealed class InitialUserCommandResponse { /** Respond with a public ack, without any content. **/ public object PublicAck : InitialUserCommandResponse() + /** Don't respond. Warning: You may not be able to respond in time! **/ + public object None : InitialUserCommandResponse() + /** * Respond with an ephemeral ack, including message content. * diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt index f7d664b1b5..e1a623bcc2 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -1,3 +1,5 @@ +@file:Suppress("StringLiteralDuplication") + package com.kotlindiscord.kord.extensions.modules.unsafe.types import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI @@ -10,8 +12,11 @@ import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.Message import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage +import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder +import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder import java.util.* @@ -20,20 +25,46 @@ import java.util.* @UnsafeAPI public interface UnsafeInteractionContext { /** Response created by acknowledging the interaction. Generic. **/ - public val interactionResponse: InteractionResponseBehavior + public var interactionResponse: InteractionResponseBehavior? + + /** Original interaction event object, for manual acks. **/ + public val event: ApplicationInteractionCreateEvent } -/** Respond to the current interaction with a public followup. **/ +/** Send an ephemeral ack, if the interaction hasn't been acknowledged yet. **/ @UnsafeAPI -public suspend inline fun UnsafeInteractionContext.respondPublic( - builder: PublicFollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage { - return when (val interaction = interactionResponse) { - is PublicInteractionResponseBehavior -> interaction.followUp(builder) - is EphemeralInteractionResponseBehavior -> interaction.followUpPublic(builder) +public suspend fun UnsafeInteractionContext.ackEphemeral( + builder: (suspend EphemeralInteractionResponseCreateBuilder.() -> Unit)? = null +): EphemeralInteractionResponseBehavior { + if (interactionResponse != null) { + error("The interaction has already been acknowledged.") + } - else -> error("Unsupported initial interaction response type - please report this.") + interactionResponse = if (builder == null) { + event.interaction.acknowledgeEphemeral() + } else { + event.interaction.respondEphemeral { builder() } } + + return interactionResponse as EphemeralInteractionResponseBehavior +} + +/** Send a public ack, if the interaction hasn't been acknowledged yet. **/ +@UnsafeAPI +public suspend fun UnsafeInteractionContext.ackPublic( + builder: (suspend PublicInteractionResponseCreateBuilder.() -> Unit)? = null +): PublicInteractionResponseBehavior { + if (interactionResponse != null) { + error("The interaction has already been acknowledged.") + } + + interactionResponse = if (builder == null) { + event.interaction.acknowledgePublic() + } else { + event.interaction.respondPublic { builder() } + } + + return interactionResponse as PublicInteractionResponseBehavior } /** Respond to the current interaction with an ephemeral followup, or throw if it isn't ephemeral. **/ @@ -41,11 +72,27 @@ public suspend inline fun UnsafeInteractionContext.respondPublic( public suspend inline fun UnsafeInteractionContext.respondEphemeral( builder: EphemeralFollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage { - val interaction = interactionResponse as? EphemeralInteractionResponseBehavior ?: error( - "Initial interaction response is not public." - ) + return when (val interaction = interactionResponse) { + is EphemeralInteractionResponseBehavior -> interaction.followUpEphemeral(builder) + is PublicInteractionResponseBehavior -> error("Initial interaction response is not public.") + + null -> error("Acknowledge the interaction before trying to follow-up.") + else -> error("Unsupported initial interaction response type - please report this.") + } +} + +/** Respond to the current interaction with a public followup. **/ +@UnsafeAPI +public suspend inline fun UnsafeInteractionContext.respondPublic( + builder: PublicFollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage { + return when (val interaction = interactionResponse) { + is PublicInteractionResponseBehavior -> interaction.followUp(builder) + is EphemeralInteractionResponseBehavior -> error("Initial interaction response is not ephemeral.") - return interaction.followUpEphemeral(builder) + null -> error("Acknowledge the interaction before trying to follow-up.") + else -> error("Unsupported initial interaction response type - please report this.") + } } /** @@ -59,6 +106,7 @@ public suspend inline fun UnsafeInteractionContext.editPublic( return when (val interaction = interactionResponse) { is PublicInteractionResponseBehavior -> interaction.edit(builder) + null -> error("Acknowledge the interaction before trying to edit it.") else -> error("Initial interaction response was not public.") } } @@ -74,6 +122,7 @@ public suspend inline fun UnsafeInteractionContext.editEphemeral( when (val interaction = interactionResponse) { is EphemeralInteractionResponseBehavior -> interaction.edit(builder) + null -> error("Acknowledge the interaction before trying to edit it.") else -> error("Initial interaction response was not ephemeral.") } } @@ -93,6 +142,7 @@ public suspend inline fun UnsafeInteractionContext.editingPaginator( is PublicInteractionResponseBehavior -> PublicResponsePaginator(pages, interaction) is EphemeralInteractionResponseBehavior -> EphemeralResponsePaginator(pages, interaction) + null -> error("Acknowledge the interaction before trying to edit it.") else -> error("Unsupported initial interaction response type - please report this.") } } @@ -112,6 +162,7 @@ public suspend inline fun UnsafeInteractionContext.respondingPaginator( return when (val interaction = interactionResponse) { is PublicInteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) + null -> error("Acknowledge the interaction before trying to follow-up.") else -> error("Initial interaction response was not public.") } } From cd72886983a7d0fbb58c3f1e57e447e89eee1c97 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 11 Sep 2021 21:52:34 +0100 Subject: [PATCH 049/131] Unsafe interactions can do unsafe things. --- .../extensions/modules/unsafe/types/UnsafeInteractionContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt index e1a623bcc2..37e736bf79 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -88,7 +88,7 @@ public suspend inline fun UnsafeInteractionContext.respondPublic( ): PublicFollowupMessage { return when (val interaction = interactionResponse) { is PublicInteractionResponseBehavior -> interaction.followUp(builder) - is EphemeralInteractionResponseBehavior -> error("Initial interaction response is not ephemeral.") + is EphemeralInteractionResponseBehavior -> interaction.followUpPublic(builder) null -> error("Acknowledge the interaction before trying to follow-up.") else -> error("Unsupported initial interaction response type - please report this.") From 885d658a6b3e701ffd1fc2fdfeb8713b36b4fcbd Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 12 Sep 2021 10:42:54 +0100 Subject: [PATCH 050/131] CommandException -> DiscordRelayedException, add ArgumentParsingException --- .../converters/MappingsVersionConverter.kt | 8 +- .../kord/extensions/Exceptions.kt | 37 ++- .../extensions/checks/types/CheckContext.kt | 10 +- .../application/ApplicationCommand.kt | 6 +- .../message/EphemeralMessageCommand.kt | 8 +- .../application/message/MessageCommand.kt | 6 +- .../message/PublicMessageCommand.kt | 8 +- .../slash/EphemeralSlashCommand.kt | 13 +- .../application/slash/PublicSlashCommand.kt | 13 +- .../application/slash/SlashCommand.kt | 6 +- .../application/slash/SlashCommandParser.kt | 89 +++++-- .../converters/impl/NumberChoiceConverter.kt | 4 +- .../application/user/EphemeralUserCommand.kt | 8 +- .../application/user/PublicUserCommand.kt | 8 +- .../commands/application/user/UserCommand.kt | 6 +- .../extensions/commands/chat/ChatCommand.kt | 17 +- .../commands/chat/ChatCommandParser.kt | 239 ++++++++++++++---- .../converters/CoalescingConverter.kt | 10 +- .../commands/converters/Converter.kt | 20 +- .../DefaultingCoalescingConverter.kt | 2 +- .../converters/DefaultingConverter.kt | 2 +- .../commands/converters/MultiConverter.kt | 2 +- .../converters/OptionalCoalescingConverter.kt | 2 +- .../commands/converters/OptionalConverter.kt | 2 +- .../commands/converters/SingleConverter.kt | 4 +- .../converters/SingleToMultiConverter.kt | 6 +- .../converters/impl/ChannelConverter.kt | 6 +- .../converters/impl/ColorConverter.kt | 8 +- .../converters/impl/DecimalConverter.kt | 4 +- .../impl/DurationCoalescingConverter.kt | 8 +- .../converters/impl/DurationConverter.kt | 8 +- .../converters/impl/EmailConverter.kt | 4 +- .../converters/impl/EmojiConverter.kt | 6 +- .../converters/impl/GuildConverter.kt | 4 +- .../commands/converters/impl/IntConverter.kt | 4 +- .../commands/converters/impl/LongConverter.kt | 4 +- .../converters/impl/MemberConverter.kt | 6 +- .../converters/impl/MessageConverter.kt | 16 +- .../commands/converters/impl/RoleConverter.kt | 6 +- .../converters/impl/SnowflakeConverter.kt | 4 +- .../converters/impl/SupportedLocale.kt | 4 +- .../commands/converters/impl/UserConverter.kt | 6 +- .../commands/events/ChatCommandEvents.kt | 3 +- .../commands/events/CommandEvents.kt | 5 +- .../commands/events/SlashCommandEvents.kt | 5 +- .../components/ComponentWithAction.kt | 10 +- .../buttons/EphemeralInteractionButton.kt | 6 +- .../buttons/PublicInteractionButton.kt | 6 +- .../components/menus/EphemeralSelectMenu.kt | 6 +- .../components/menus/PublicSelectMenu.kt | 6 +- .../extensions/sentry/SentryIdConverter.kt | 4 +- .../java/J8DurationCoalescingConverter.kt | 8 +- .../modules/time/java/J8DurationConverter.kt | 8 +- .../time4j/T4JDurationCoalescingConverter.kt | 6 +- .../time/time4j/T4JDurationConverter.kt | 6 +- .../unsafe/commands/UnsafeCommandEvents.kt | 3 +- .../unsafe/commands/UnsafeMessageCommand.kt | 8 +- .../unsafe/commands/UnsafeSlashCommand.kt | 13 +- .../unsafe/commands/UnsafeUserCommand.kt | 8 +- .../unsafe/converters/UnionConverter.kt | 4 +- 60 files changed, 485 insertions(+), 264 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt index 91321b4175..21733d58dd 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.modules.extra.mappings.converters -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -35,14 +35,14 @@ class MappingsVersionConverter( val version = namespace.getProvider(arg).getOrNull() // if (version == null) { -// throw CommandException("Invalid ${namespace.id} version: `$arg`") +// throw DiscordRelayedException("Invalid ${namespace.id} version: `$arg`") // // val created = namespace.createAndAdd(arg) // // if (created != null) { // this.parsed = created // } else { -// throw CommandException("Invalid ${namespace.id} version: `$arg`") +// throw DiscordRelayedException("Invalid ${namespace.id} version: `$arg`") // } // } else { // this.parsed = version @@ -55,7 +55,7 @@ class MappingsVersionConverter( } } - throw CommandException("Invalid ${namespace.id} version: `$arg`") + throw DiscordRelayedException("Invalid ${namespace.id} version: `$arg`") } override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt index a935dbe48b..af6b8d68e6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/Exceptions.kt @@ -1,8 +1,11 @@ package com.kotlindiscord.kord.extensions +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.events.EventHandler import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.parser.StringParser import kotlin.reflect.KClass /** @@ -82,14 +85,40 @@ public class CommandRegistrationException(public val name: String?, public val r } /** - * Thrown when something bad happens during command processing. + * Thrown when something exceptional happens that the actioning user on Discord needs to be aware of. * * Provided [reason] will be returned to the user verbatim. * - * @param reason Human-readable reason for the failure. + * @param reason Human-readable reason for the failure. May be translated. + * @param translationKey Translation key used to create the [reason] string, if any. */ -public open class CommandException(public var reason: String) : ExtensionsException() { - public constructor(other: CommandException) : this(other.reason) +public open class DiscordRelayedException( + public open val reason: String, + public open val translationKey: String? = null +) : ExtensionsException() { + public constructor(other: DiscordRelayedException) : this(other.reason) + + override fun toString(): String = reason +} + +/** + * Thrown when something happens during argument parsing. + * + * @param reason Human-readable reason for the failure. May be translated. + * @param translationKey Translation key used to create the [reason] string, if any. + * @param argument Current Argument object, if any. + * @param arguments Arguments object for the command. + * @param parser Tokenizing string parser used for this parse attempt, if this was a chat command. + */ +public open class ArgumentParsingException( + public override val reason: String, + public override val translationKey: String?, + public val argument: Argument<*>?, + public val arguments: Arguments, + public val parser: StringParser? +) : DiscordRelayedException(reason, translationKey) { + public constructor(other: ArgumentParsingException) : + this(other.reason, other.translationKey, other.argument, other.arguments, other.parser) override fun toString(): String = reason } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index d09e7ff240..9b058b6521 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.checks.types -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.core.event.Event import org.koin.core.component.KoinComponent @@ -135,11 +135,13 @@ public class CheckContext(public val event: T, public val locale: public fun translate(key: String, bundle: String? = null, replacements: Array = arrayOf()): String = translations.translate(key, locale, bundleName = bundle, replacements = replacements) - /** If this check has failed and a message is set, throw a `CommandException` with the translated message. **/ - @Throws(CommandException::class) + /** + * If this check has failed and a message is set, throw a [DiscordRelayedException] with the translated message. + */ + @Throws(DiscordRelayedException::class) public fun throwIfFailedWithMessage() { if (passed.not() && message != null) { - throw CommandException( + throw DiscordRelayedException( translate("checks.responseTemplate", replacements = arrayOf(message)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 56eb06319d..47a7e9599b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext @@ -198,7 +198,7 @@ public abstract class ApplicationCommand( } /** Runs standard checks that can be handled in a generic way, without worrying about subclass-specific checks. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun runStandardChecks(event: E): Boolean { val locale = event.getLocale() @@ -230,7 +230,7 @@ public abstract class ApplicationCommand( } /** Override this in order to implement any subclass-specific checks. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun runChecks(event: E): Boolean = runStandardChecks(event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index 756bd90c63..9270a886c8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandFailedChecksEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandFailedWithExceptionEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandInvocationEvent @@ -43,7 +43,7 @@ public class EphemeralMessageCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondEphemeral { content = e.reason } emitEventAsync(EphemeralMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -65,7 +65,7 @@ public class EphemeralMessageCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(EphemeralMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -75,7 +75,7 @@ public class EphemeralMessageCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index b34096182b..fc7c0fd86c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand @@ -51,7 +51,7 @@ public abstract class MessageCommand>( public abstract suspend fun respondText(context: C, message: String) /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) @@ -60,7 +60,7 @@ public abstract class MessageCommand>( val missingPerms = requiredPerms.filter { !perms.contains(it) } if (missingPerms.isNotEmpty()) { - throw CommandException( + throw DiscordRelayedException( context.translate( "commands.error.missingBotPermissions", null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index fe4ab5e8dd..e41df749c8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandFailedChecksEvent import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandFailedWithExceptionEvent import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandInvocationEvent @@ -43,7 +43,7 @@ public class PublicMessageCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -65,7 +65,7 @@ public class PublicMessageCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -75,7 +75,7 @@ public class PublicMessageCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index 14bc3702a5..ad4580284f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.application.slash -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension @@ -73,7 +74,7 @@ public class EphemeralSlashCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondEphemeral { content = e.reason } emitEventAsync( @@ -101,7 +102,7 @@ public class EphemeralSlashCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync( @@ -119,9 +120,9 @@ public class EphemeralSlashCommand( val args = registry.argumentParser.parse(arguments, context) context.populateArgs(args) - } catch (e: CommandException) { + } catch (e: ArgumentParsingException) { respondText(context, e.reason) - emitEventAsync(EphemeralSlashCommandFailedParsingEvent(this, event, e.reason)) + emitEventAsync(EphemeralSlashCommandFailedParsingEvent(this, event, e)) return } @@ -130,7 +131,7 @@ public class EphemeralSlashCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index e08deaa7f8..2ea16f44ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.application.slash -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension @@ -73,7 +74,7 @@ public class PublicSlashCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -95,7 +96,7 @@ public class PublicSlashCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -106,9 +107,9 @@ public class PublicSlashCommand( val args = registry.argumentParser.parse(arguments, context) context.populateArgs(args) - } catch (e: CommandException) { + } catch (e: ArgumentParsingException) { respondText(context, e.reason) - emitEventAsync(PublicSlashCommandFailedParsingEvent(this, event, e.reason)) + emitEventAsync(PublicSlashCommandFailedParsingEvent(this, event, e)) return } @@ -117,7 +118,7 @@ public class PublicSlashCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 61fd139880..62bf1c9dfb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.slash -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.Arguments @@ -109,7 +109,7 @@ public abstract class SlashCommand, A : Arguments> public abstract suspend fun run(event: ChatInputCommandInteractionCreateEvent) /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) @@ -118,7 +118,7 @@ public abstract class SlashCommand, A : Arguments> val missingPerms = requiredPerms.filter { !perms.contains(it) } if (missingPerms.isNotEmpty()) { - throw CommandException( + throw DiscordRelayedException( context.translate( "commands.error.missingBotPermissions", null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt index 2e01d1d316..542ad01cfe 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt @@ -5,7 +5,8 @@ package com.kotlindiscord.kord.extensions.commands.application.slash -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.* @@ -80,7 +81,7 @@ public open class SlashCommandParser { } if (converter.required && !parsed) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", replacements = arrayOf( @@ -88,7 +89,13 @@ public open class SlashCommandParser { converter.getErrorString(context), currentValue ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + null ) } @@ -100,9 +107,16 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required) { - throw CommandException(converter.handleError(e, context)) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null + ) } } catch (t: Throwable) { logger.debug { "Argument ${currentArg.displayName} threw: $t" } @@ -120,7 +134,7 @@ public open class SlashCommandParser { } if (converter.required && !parsed) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", replacements = arrayOf( @@ -128,7 +142,13 @@ public open class SlashCommandParser { converter.getErrorString(context), currentValue ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + null ) } @@ -140,9 +160,16 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required) { - throw CommandException(converter.handleError(e, context)) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null + ) } } catch (t: Throwable) { logger.debug { "Argument ${currentArg.displayName} threw: $t" } @@ -167,10 +194,15 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError) { - throw CommandException( - converter.handleError(e, context) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null ) } } catch (t: Throwable) { @@ -192,10 +224,15 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError) { - throw CommandException( - converter.handleError(e, context) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null ) } } catch (t: Throwable) { @@ -217,10 +254,15 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError) { - throw CommandException( - converter.handleError(e, context) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null ) } } catch (t: Throwable) { @@ -242,10 +284,15 @@ public open class SlashCommandParser { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError) { - throw CommandException( - converter.handleError(e, context) + throw ArgumentParsingException( + converter.handleError(e, context), + null, + + currentArg, + argumentsObj, + null ) } } catch (t: Throwable) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt index a8a527f181..d3add1ade8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter @@ -56,7 +56,7 @@ class NumberChoiceConverter( context.translate("converters.number.error.invalid.otherBase", replacements = arrayOf(arg, radix)) } - throw CommandException(errorString) + throw DiscordRelayedException(errorString) } return true diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 54707e707e..eca67205ae 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFailedChecksEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFailedWithExceptionEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandInvocationEvent @@ -43,7 +43,7 @@ public class EphemeralUserCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondEphemeral { content = e.reason } emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) @@ -65,7 +65,7 @@ public class EphemeralUserCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) @@ -75,7 +75,7 @@ public class EphemeralUserCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 31e9d7319c..35da0bbc7d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailedChecksEvent import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailedWithExceptionEvent import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandInvocationEvent @@ -43,7 +43,7 @@ public class PublicUserCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) @@ -65,7 +65,7 @@ public class PublicUserCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) @@ -75,7 +75,7 @@ public class PublicUserCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 63a39aeaeb..93c07afe8c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand @@ -51,7 +51,7 @@ public abstract class UserCommand>( public abstract suspend fun respondText(context: C, message: String) /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) @@ -60,7 +60,7 @@ public abstract class UserCommand>( val missingPerms = requiredPerms.filter { !perms.contains(it) } if (missingPerms.isNotEmpty()) { - throw CommandException( + throw DiscordRelayedException( context.translate( "commands.error.missingBotPermissions", null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 1dfad8defd..48512f174c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.commands.chat -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.checks.types.Check @@ -325,7 +326,7 @@ public open class ChatCommand( } /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ - @Throws(CommandException::class) + @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: ChatCommandContext) { if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) @@ -334,7 +335,7 @@ public open class ChatCommand( val missingPerms = requiredPerms.filter { !perms.contains(it) } if (missingPerms.isNotEmpty()) { - throw CommandException( + throw DiscordRelayedException( context.translate( "commands.error.missingBotPermissions", null, @@ -384,7 +385,7 @@ public open class ChatCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) event.message.respond(e.reason) @@ -423,7 +424,7 @@ public open class ChatCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.message.respond(e.reason) emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) @@ -434,9 +435,9 @@ public open class ChatCommand( try { val parsedArgs = registry.parser.parse(this.arguments!!, context) context.populateArgs(parsedArgs) - } catch (e: CommandException) { + } catch (e: ArgumentParsingException) { event.message.respond(e.reason) - emitEventAsync(ChatCommandFailedParsingEvent(this, event, e.reason)) + emitEventAsync(ChatCommandFailedParsingEvent(this, event, e)) return } @@ -445,7 +446,7 @@ public open class ChatCommand( try { this.body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { event.message.respond(t.reason) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt index 1178b5bc19..3c6debd44e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt @@ -7,7 +7,8 @@ package com.kotlindiscord.kord.extensions.commands.chat -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments @@ -63,7 +64,7 @@ public open class ChatCommandParser : KoinComponent { * @param context MessageCommand context for this command invocation. * * @return Built [Arguments] object, with converters filled. - * @throws CommandException Thrown based on a lot of possible cases. This is intended for display on Discord. + * @throws DiscordRelayedException Thrown based on a lot of possible cases. This is intended for display on Discord. */ public open suspend fun parse(builder: () -> T, context: ChatCommandContext<*>): T { val argumentsObj = builder.invoke() @@ -107,11 +108,17 @@ public open class ChatCommandParser : KoinComponent { is SingleConverter<*> -> try { val parsed = if (hasKwargs) { if (kwValue!!.size != 1) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.requiresOneValue", replacements = arrayOf(currentArg.displayName, kwValue.size) - ) + ), + + "argumentParser.error.requiresOneValue", + + currentArg, + argumentsObj, + parser ) } @@ -121,7 +128,7 @@ public open class ChatCommandParser : KoinComponent { } if ((converter.required || hasKwargs) && !parsed) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", @@ -129,7 +136,13 @@ public open class ChatCommandParser : KoinComponent { currentArg.displayName, converter.getErrorString(context), ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + parser ) } @@ -140,9 +153,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || hasKwargs) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -151,7 +164,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -165,11 +184,17 @@ public open class ChatCommandParser : KoinComponent { is DefaultingConverter<*> -> try { val parsed = if (hasKwargs) { if (kwValue!!.size != 1) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.requiresOneValue", replacements = arrayOf(currentArg.displayName, kwValue.size) - ) + ), + + "argumentParser.error.requiresOneValue", + + currentArg, + argumentsObj, + parser ) } @@ -185,9 +210,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError || hasKwargs) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -196,7 +221,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -206,11 +237,17 @@ public open class ChatCommandParser : KoinComponent { is OptionalConverter<*> -> try { val parsed = if (hasKwargs) { if (kwValue!!.size != 1) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.requiresOneValue", replacements = arrayOf(currentArg.displayName, kwValue.size) - ) + ), + + "argumentParser.error.requiresOneValue", + + currentArg, + argumentsObj, + parser ) } @@ -226,9 +263,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError || hasKwargs) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -237,7 +274,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -256,7 +299,7 @@ public open class ChatCommandParser : KoinComponent { } if ((converter.required || hasKwargs) && parsedCount <= 0) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", @@ -264,13 +307,19 @@ public open class ChatCommandParser : KoinComponent { currentArg.displayName, converter.getErrorString(context) ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + parser ) } if (hasKwargs) { if (parsedCount < kwValue!!.size) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.notAllValid", @@ -280,7 +329,13 @@ public open class ChatCommandParser : KoinComponent { parsedCount, context.translate(converter.signatureTypeString, bundleName = converter.bundle) ) - ) + ), + + "argumentParser.error.notAllValid", + + currentArg, + argumentsObj, + parser ) } @@ -294,9 +349,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -305,7 +360,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -324,7 +385,7 @@ public open class ChatCommandParser : KoinComponent { } if ((converter.required || hasKwargs) && parsedCount <= 0) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", @@ -332,13 +393,19 @@ public open class ChatCommandParser : KoinComponent { currentArg.displayName, converter.getErrorString(context), ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + parser ) } if (hasKwargs) { if (parsedCount < kwValue!!.size) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.notAllValid", @@ -348,7 +415,13 @@ public open class ChatCommandParser : KoinComponent { parsedCount, context.translate(converter.signatureTypeString, bundleName = converter.bundle) ) - ) + ), + + "argumentParser.error.notAllValid", + + currentArg, + argumentsObj, + parser ) } @@ -362,9 +435,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -373,7 +446,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -392,7 +471,7 @@ public open class ChatCommandParser : KoinComponent { } if ((converter.required || hasKwargs) && parsedCount <= 0) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", @@ -400,13 +479,19 @@ public open class ChatCommandParser : KoinComponent { currentArg.displayName, converter.getErrorString(context), ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + parser ) } if (hasKwargs) { if (parsedCount < kwValue!!.size) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.notAllValid", @@ -416,7 +501,13 @@ public open class ChatCommandParser : KoinComponent { parsedCount, context.translate(converter.signatureTypeString, bundleName = converter.bundle) ) - ) + ), + + "argumentParser.error.notAllValid", + + currentArg, + argumentsObj, + parser ) } @@ -430,9 +521,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError || hasKwargs) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -441,7 +532,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -460,7 +557,7 @@ public open class ChatCommandParser : KoinComponent { } if ((converter.required || hasKwargs) && parsedCount <= 0) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.invalidValue", @@ -468,13 +565,19 @@ public open class ChatCommandParser : KoinComponent { currentArg.displayName, converter.getErrorString(context), ) - ) + ), + + "argumentParser.error.invalidValue", + + currentArg, + argumentsObj, + parser ) } if (hasKwargs) { if (parsedCount < kwValue!!.size) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.notAllValid", @@ -484,7 +587,13 @@ public open class ChatCommandParser : KoinComponent { parsedCount, context.translate(converter.signatureTypeString, bundleName = converter.bundle) ) - ) + ), + + "argumentParser.error.notAllValid", + + currentArg, + argumentsObj, + parser ) } @@ -498,9 +607,9 @@ public open class ChatCommandParser : KoinComponent { converter.validate(context) } } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { if (converter.required || converter.outputError || hasKwargs) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -509,7 +618,13 @@ public open class ChatCommandParser : KoinComponent { converter.handleError(e, context) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } catch (t: Throwable) { @@ -520,7 +635,7 @@ public open class ChatCommandParser : KoinComponent { } } - else -> throw CommandException( + else -> throw ArgumentParsingException( context.translate( "argumentParser.error.errorInArgument", @@ -532,7 +647,13 @@ public open class ChatCommandParser : KoinComponent { replacements = arrayOf(currentArg.converter) ) ) - ) + ), + + "argumentParser.error.errorInArgument", + + currentArg, + argumentsObj, + parser ) } } @@ -544,17 +665,23 @@ public open class ChatCommandParser : KoinComponent { if (filledRequiredArgs < allRequiredArgs) { if (filledRequiredArgs < 1) { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.noFilledArguments", replacements = arrayOf( allRequiredArgs ) - ) + ), + + "argumentParser.error.noFilledArguments", + + null, + argumentsObj, + parser ) } else { - throw CommandException( + throw ArgumentParsingException( context.translate( "argumentParser.error.someFilledArguments", @@ -562,7 +689,13 @@ public open class ChatCommandParser : KoinComponent { allRequiredArgs, filledRequiredArgs ) - ) + ), + + "argumentParser.error.someFilledArguments", + + null, + argumentsObj, + parser ) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingConverter.kt index 1698d24153..3ab46e0eeb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingConverter.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.converters -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import dev.kord.common.annotation.KordPreview /** @@ -19,10 +19,10 @@ import dev.kord.common.annotation.KordPreview * You can create a coalescing converter of your own by extending this class. * * @property shouldThrow Intended only for use if this converter is the last one in a set of arguments, if this is - * `true` then the converter should throw a [CommandException] when an argument can't be parsed, instead of just + * `true` then the converter should throw a [DiscordRelayedException] when an argument can't be parsed, instead of just * stopping and allowing parsing to continue. * - * @property validator Validation lambda, which may throw a [CommandException] if required. + * @property validator Validation lambda, which may throw a [DiscordRelayedException] if required. */ public abstract class CoalescingConverter( public open val shouldThrow: Boolean = false, @@ -54,8 +54,8 @@ public abstract class CoalescingConverter( * provides. * * @param outputError Optionally, provide `true` to fail parsing and return errors if the converter throws a - * [CommandException], instead of continuing. You probably only want to set this if the converter is the last one - * in a set of arguments. + * [DiscordRelayedException], instead of continuing. You probably only want to set this if the converter is the + * last one in a set of arguments. */ @ConverterToOptional public open fun toOptional( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt index b3066a83c6..d8b6c5fe6d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/Converter.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.commands.converters -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments @@ -47,7 +47,7 @@ public abstract class Converter = null /** This will be set to true by the argument parser if the conversion succeeded. **/ @@ -86,13 +86,13 @@ public abstract class Converter( defaultValue: T, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/DefaultingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/DefaultingConverter.kt index 39b0b11c48..4927e73494 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/DefaultingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/DefaultingConverter.kt @@ -13,7 +13,7 @@ import dev.kord.common.annotation.KordPreview * You can create a defaulting converter of your own by extending this class. * * @property outputError Whether the argument parser should output parsing errors on invalid arguments. - * @property validator Validation lambda, which may throw a CommandException if required. + * @property validator Validation lambda, which may throw a DiscordRelayedException if required. */ public abstract class DefaultingConverter( defaultValue: T, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/MultiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/MultiConverter.kt index d93c1207df..9e2d6d1cbc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/MultiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/MultiConverter.kt @@ -14,7 +14,7 @@ import dev.kord.common.annotation.KordPreview * * You can create a multi converter of your own by extending this class. * - * @property validator Validation lambda, which may throw a CommandException if required. + * @property validator Validation lambda, which may throw a DiscordRelayedException if required. */ public abstract class MultiConverter( required: Boolean = true, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalCoalescingConverter.kt index 8c5e23cd50..7f4bfa8445 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalCoalescingConverter.kt @@ -17,7 +17,7 @@ import dev.kord.common.annotation.KordPreview * You can create an optional coalescing converter of your own by extending this class. * * @property outputError Whether the argument parser should output parsing errors on invalid arguments. - * @property validator Validation lambda, which may throw a CommandException if required. + * @property validator Validation lambda, which may throw a DiscordRelayedException if required. */ public abstract class OptionalCoalescingConverter( public val outputError: Boolean = false, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalConverter.kt index 9c770cff34..94e0fd9659 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/OptionalConverter.kt @@ -10,7 +10,7 @@ import dev.kord.common.annotation.KordPreview * This works just like [SingleConverter], but the value can be nullable and it can never be required. * * @property outputError Whether the argument parser should output parsing errors on invalid arguments. - * @property validator Validation lambda, which may throw a CommandException if required. + * @property validator Validation lambda, which may throw a DiscordRelayedException if required. */ public abstract class OptionalConverter( public val outputError: Boolean = false, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleConverter.kt index 18dc8ac389..a732a6a58d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleConverter.kt @@ -15,7 +15,7 @@ import dev.kord.common.annotation.KordPreview * * You can create a single converter of your own by extending this class. * - * @property validator Validation lambda, which may throw a CommandException if required. + * @property validator Validation lambda, which may throw a DiscordRelayedException if required. */ public abstract class SingleConverter( override var validator: Validator = null @@ -82,7 +82,7 @@ public abstract class SingleConverter( * provides. * * @param outputError Optionally, provide `true` to fail parsing and return errors if the converter throws a - * [CommandException], instead of continuing. + * [DiscordRelayedException] (instead of continuing). */ @ConverterToOptional public open fun toOptional( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt index 449f58d1d1..903d4b9caa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToMultiConverter.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.converters -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.parser.StringParser @@ -52,7 +52,7 @@ public class SingleToMultiConverter( values.add(value) parser?.parseNext() // Move the cursor ahead - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { break } } @@ -68,7 +68,7 @@ public class SingleToMultiConverter( val value = singleConverter.getValue(dummyArgs, singleConverter::parsed) values.add(value) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { break } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt index 72df147a0b..6b8f1229bd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt @@ -7,7 +7,7 @@ ) package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -58,7 +58,7 @@ public class ChannelConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false val channel: Channel = findChannel(arg, context) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate( "converters.channel.error.missing", replacements = arrayOf(arg) @@ -76,7 +76,7 @@ public class ChannelConverter( try { kord.getChannel(Snowflake(id.toLong())) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate( "converters.channel.error.invalid", replacements = arrayOf(id) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt index c539509287..64b7375305 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt @@ -9,7 +9,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -47,14 +47,14 @@ public class ColorConverter( arg.startsWith("0x") -> this.parsed = Color(arg.substring(2).toInt(16)) arg.all { it.isDigit() } -> this.parsed = Color(arg.toInt()) - else -> this.parsed = ColorParser.parse(arg, context.getLocale()) ?: throw CommandException( + else -> this.parsed = ColorParser.parse(arg, context.getLocale()) ?: throw DiscordRelayedException( context.translate("converters.color.error.unknown", replacements = arrayOf(arg)) ) } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { throw e } catch (t: Throwable) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.color.error.unknownOrFailed", replacements = arrayOf(arg)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt index f4da85e011..2162fe1f2e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -41,7 +41,7 @@ public class DecimalConverter( try { this.parsed = arg.toDouble() } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.decimal.error.invalid", replacements = arrayOf(arg)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt index eab42cb30d..ad8b5198cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.CoalescingConverter @@ -125,7 +125,7 @@ public class DurationCoalescingConverter( val applied: Instant = now.plus(result, TimeZone.UTC) if (now > applied) { - throw CommandException(context.translate("converters.duration.error.positiveOnly")) + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) } } @@ -154,10 +154,10 @@ public class DurationCoalescingConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } - is DurationParserException -> throw CommandException(e.error) + is DurationParserException -> throw DiscordRelayedException(e.error) else -> throw e } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt index 84468c30f1..d785527620 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter @@ -52,7 +52,7 @@ public class DurationConverter( val applied: Instant = now.plus(result, TimeZone.UTC) if (now > applied) { - throw CommandException(context.translate("converters.duration.error.positiveOnly")) + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) } } @@ -63,9 +63,9 @@ public class DurationConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } catch (e: DurationParserException) { - throw CommandException(e.error) + throw DiscordRelayedException(e.error) } return true diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt index e967684009..b8263021f7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -37,7 +37,7 @@ public class EmailConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false if (!EmailValidator.getInstance().isValid(arg)) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.email.error.invalid", replacements = arrayOf(arg)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt index 46113fad10..ada3258bb1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -51,7 +51,7 @@ public class EmojiConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false val emoji: GuildEmoji = findEmoji(arg, context) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate("converters.emoji.error.missing", replacements = arrayOf(arg)) ) @@ -70,7 +70,7 @@ public class EmojiConverter( it.getEmojiOrNull(snowflake) }.firstOrNull() } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.emoji.error.invalid", replacements = arrayOf(id)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt index 1a57429687..9daa34d2cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -46,7 +46,7 @@ public class GuildConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false this.parsed = findGuild(arg) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate("converters.guild.error.missing", replacements = arrayOf(arg)) ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt index d58bb1adf0..f1fefe663c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -48,7 +48,7 @@ public class IntConverter( context.translate("converters.number.error.invalid.otherBase", replacements = arrayOf(arg, radix)) } - throw CommandException(errorString) + throw DiscordRelayedException(errorString) } return true diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt index fa8646fccc..481129e386 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -48,7 +48,7 @@ public class LongConverter( context.translate("converters.number.error.invalid.otherBase", replacements = arrayOf(arg, radix)) } - throw CommandException(errorString) + throw DiscordRelayedException(errorString) } return true diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt index 47346f73a9..885d559491 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext @@ -71,7 +71,7 @@ public class MemberConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false parsed = findMember(arg, context) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate("converters.member.error.missing", replacements = arrayOf(arg)) ) @@ -85,7 +85,7 @@ public class MemberConverter( try { kord.getUser(Snowflake(id)) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.member.error.invalid", replacements = arrayOf(id)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt index 43e3428d17..75adc92412 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext @@ -96,7 +96,7 @@ public class MessageConverter( @Suppress("MagicNumber") if (split.size < 3) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.message.error.invalidUrl", replacements = arrayOf(arg)) ) } @@ -105,7 +105,7 @@ public class MessageConverter( val gid: Snowflake = try { Snowflake(split[0]) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.message.error.invalidGuildId", replacements = arrayOf(split[0])) ) } @@ -120,7 +120,7 @@ public class MessageConverter( val cid: Snowflake = try { Snowflake(split[1]) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate( "converters.message.error.invalidChannelId", replacements = arrayOf(split[1]) @@ -146,7 +146,7 @@ public class MessageConverter( val mid: Snowflake = try { Snowflake(split[2]) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate( "converters.message.error.invalidMessageId", replacements = arrayOf(split[2]) @@ -177,7 +177,7 @@ public class MessageConverter( try { channel.getMessage(Snowflake(arg)) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate( "converters.message.error.invalidMessageId", replacements = arrayOf(arg) @@ -193,6 +193,8 @@ public class MessageConverter( StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } private suspend fun errorNoMessage(arg: String, context: CommandContext): Nothing { - throw CommandException(context.translate("converters.message.error.missing", replacements = arrayOf(arg))) + throw DiscordRelayedException( + context.translate("converters.message.error.missing", replacements = arrayOf(arg)) + ) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt index a8bc5a68a3..46d826d93c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -51,7 +51,7 @@ public class RoleConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false parsed = findRole(arg, context) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate("converters.role.error.missing", replacements = arrayOf(arg)) ) @@ -74,7 +74,7 @@ public class RoleConverter( try { guild.getRole(Snowflake(id)) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.role.error.invalid", replacements = arrayOf(id)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt index e07d9c20b0..99851e70b8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -42,7 +42,7 @@ public class SnowflakeConverter( try { this.parsed = Snowflake(arg) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.snowflake.error.invalid", replacements = arrayOf(arg)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt index 28764b871b..1dc3f967dc 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt @@ -9,7 +9,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.* @@ -45,7 +45,7 @@ public class SupportedLocale( override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { val arg: String = named ?: parser?.parseNext()?.data ?: return false - this.parsed = SupportedLocales.ALL_LOCALES[arg.lowercase().trim()] ?: throw CommandException( + this.parsed = SupportedLocales.ALL_LOCALES[arg.lowercase().trim()] ?: throw DiscordRelayedException( context.translate("converters.supportedLocale.error.unknown", replacements = arrayOf(arg)) ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt index 79209db28d..bc3838948f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.commands.converters.impl -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandContext @@ -66,7 +66,7 @@ public class UserConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false this.parsed = findUser(arg, context) - ?: throw CommandException( + ?: throw DiscordRelayedException( context.translate("converters.user.error.missing", replacements = arrayOf(arg)) ) @@ -80,7 +80,7 @@ public class UserConverter( try { kord.getUser(Snowflake(id)) } catch (e: NumberFormatException) { - throw CommandException( + throw DiscordRelayedException( context.translate("converters.user.error.invalid", replacements = arrayOf(id)) ) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt index 9522467733..e6496370eb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/ChatCommandEvents.kt @@ -1,5 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.events +import com.kotlindiscord.kord.extensions.ArgumentParsingException import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import dev.kord.core.event.message.MessageCreateEvent @@ -26,7 +27,7 @@ public data class ChatCommandFailedChecksEvent( public data class ChatCommandFailedParsingEvent( override val command: ChatCommand<*>, override val event: MessageCreateEvent, - override val reason: String, + override val exception: ArgumentParsingException, ) : CommandFailedParsingEvent, MessageCreateEvent> /** Event emitted when a chat command's invocation fails with an exception. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt index a9cd586057..408a01a565 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/CommandEvents.kt @@ -1,5 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.events +import com.kotlindiscord.kord.extensions.ArgumentParsingException import com.kotlindiscord.kord.extensions.commands.Command import com.kotlindiscord.kord.extensions.events.KordExEvent import dev.kord.core.event.Event @@ -35,8 +36,8 @@ public sealed interface CommandFailedChecksEvent : Comma /** Basic event emitted when a command's argument parsing fails. **/ public sealed interface CommandFailedParsingEvent : CommandFailedEvent { - /** Human-readable failure reason. **/ - public val reason: String + /** Argument parsing exception object. **/ + public val exception: ArgumentParsingException } /** Basic event emitted when a command's body invocation fails with an exception. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt index a0817cdca0..373d08b0a6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/events/SlashCommandEvents.kt @@ -1,5 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.events +import com.kotlindiscord.kord.extensions.ArgumentParsingException import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommand import com.kotlindiscord.kord.extensions.commands.application.slash.PublicSlashCommand import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand @@ -77,14 +78,14 @@ public interface SlashCommandFailedParsingEvent> : public data class EphemeralSlashCommandFailedParsingEvent( override val command: EphemeralSlashCommand<*>, override val event: ChatInputCommandInteractionCreateEvent, - override val reason: String, + override val exception: ArgumentParsingException, ) : SlashCommandFailedParsingEvent> /** Event emitted when a public slash command's argument parsing fails'. **/ public data class PublicSlashCommandFailedParsingEvent( override val command: PublicSlashCommand<*>, override val event: ChatInputCommandInteractionCreateEvent, - override val reason: String, + override val exception: ArgumentParsingException, ) : SlashCommandFailedParsingEvent> /** Basic event emitted when a slash command invocation fails with an exception. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index 4dc0421731..bfd9c91a8c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.components -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.utils.getLocale @@ -82,7 +82,7 @@ public abstract class ComponentWithAction() { try { this.parsed = SentryId(arg) } catch (e: IllegalArgumentException) { - throw CommandException( + throw DiscordRelayedException( context.translate("extensions.sentry.converter.error.invalid", replacements = arrayOf(arg)) ) } diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt index c9f6d6c94c..f2418382c9 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.modules.time.java -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -123,7 +123,7 @@ public class J8DurationCoalescingConverter( normalized.normalize(LocalDateTime.now()) if (!normalized.isPositive()) { - throw CommandException(context.translate("converters.duration.error.positiveOnly")) + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) } } @@ -152,10 +152,10 @@ public class J8DurationCoalescingConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } - is DurationParserException -> throw CommandException(e.error) + is DurationParserException -> throw DiscordRelayedException(e.error) else -> throw e } diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt index 34caad44ed..e35d3ef8c5 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.modules.time.java -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -50,7 +50,7 @@ public class J8DurationConverter( normalized.normalize(LocalDateTime.now()) if (!normalized.isPositive()) { - throw CommandException(context.translate("converters.duration.error.positiveOnly")) + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) } } @@ -61,9 +61,9 @@ public class J8DurationConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } catch (e: DurationParserException) { - throw CommandException(e.error) + throw DiscordRelayedException(e.error) } return true diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt index d0baafbad6..12542dc341 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.modules.time.time4j -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -139,10 +139,10 @@ public class T4JDurationCoalescingConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } - is DurationParserException -> throw CommandException(e.error) + is DurationParserException -> throw DiscordRelayedException(e.error) else -> throw e } diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt index 96df78ab7d..b5530a2314 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.modules.time.time4j -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -50,9 +50,9 @@ public class T4JDurationConverter( replacements = arrayOf(e.unit) ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" - throw CommandException(message) + throw DiscordRelayedException(message) } catch (e: DurationParserException) { - throw CommandException(e.error) + throw DiscordRelayedException(e.error) } return true diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt index 2fb81aaf3b..86537b6d41 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeCommandEvents.kt @@ -2,6 +2,7 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.commands +import com.kotlindiscord.kord.extensions.ArgumentParsingException import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent @@ -63,7 +64,7 @@ public data class UnsafeSlashCommandFailedChecksEvent( public data class UnsafeSlashCommandFailedParsingEvent( override val command: UnsafeSlashCommand<*>, override val event: ChatInputCommandInteractionCreateEvent, - override val reason: String + override val exception: ArgumentParsingException, ) : SlashCommandFailedParsingEvent> /** Event emitted when an unsafe slash command invocation fails with an exception. **/ diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt index 8e6151dfef..43cb939517 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.commands -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.application.message.MessageCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI @@ -38,7 +38,7 @@ public class UnsafeMessageCommand( ) return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -69,7 +69,7 @@ public class UnsafeMessageCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -80,7 +80,7 @@ public class UnsafeMessageCommand( checkBotPerms(context) body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt index 7f00fb9323..b7bcee5a2f 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -2,7 +2,8 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.commands -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.application.slash.SlashGroup @@ -73,7 +74,7 @@ public class UnsafeSlashCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -104,7 +105,7 @@ public class UnsafeSlashCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -117,9 +118,9 @@ public class UnsafeSlashCommand( context.populateArgs(args) } - } catch (e: CommandException) { + } catch (e: ArgumentParsingException) { respondText(context, e.reason) - emitEventAsync(UnsafeSlashCommandFailedParsingEvent(this, event, e.reason)) + emitEventAsync(UnsafeSlashCommandFailedParsingEvent(this, event, e)) return } @@ -127,7 +128,7 @@ public class UnsafeSlashCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt index f33773d827..0297976c9b 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.commands -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI @@ -39,7 +39,7 @@ public class UnsafeUserCommand( return } - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { event.interaction.respondPublic { content = e.reason } emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) @@ -70,7 +70,7 @@ public class UnsafeUserCommand( try { checkBotPerms(context) - } catch (e: CommandException) { + } catch (e: DiscordRelayedException) { respondText(context, e.reason) emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) @@ -80,7 +80,7 @@ public class UnsafeUserCommand( try { body(context) } catch (t: Throwable) { - if (t is CommandException) { + if (t is DiscordRelayedException) { respondText(context, t.reason) } diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt index 14173285d7..57143b8b81 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt @@ -7,7 +7,7 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.converters -import com.kotlindiscord.kord.extensions.CommandException +import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext @@ -163,7 +163,7 @@ public class UnionConverter( if (shouldThrow) throw t } - else -> throw CommandException( + else -> throw DiscordRelayedException( context.translate( "converters.union.error.unknownConverterType", replacements = arrayOf(converter) From 1c7ed62bfdd03906c51d3b85942a61027d829fcd Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 12 Sep 2021 14:35:58 +0100 Subject: [PATCH 051/131] Fix English translations --- .../translations/kordex/strings.properties | 24 +++--- .../kordex/strings_en_GB.properties | 75 ++++++++++++++++--- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 34710cff8a..3126542353 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -6,18 +6,18 @@ argumentParser.error.unknownConverterType=Unknown converter type provided\: `{0} argumentParser.error.noFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}. argumentParser.error.someFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}, but only {1} could be filled. -channelType.dm="DM" -channelType.groupDm="Group DM" -channelType.guildCategory="Category" -channelType.guildNews="News" -channelType.guildStageVoice="Stage" -channelType.guildStore="Store" -channelType.guildText="Text" -channelType.guildVoice="Voice" -channelType.publicNewsThread="Voice" -channelType.publicGuildThread="Voice" -channelType.privateThread="Voice" -channelType.unknown="Unknown" +channelType.dm=DM +channelType.groupDm=Group DM +channelType.guildCategory=Category +channelType.guildNews=News +channelType.guildStageVoice=Stage +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=Voice +channelType.publicGuildThread=Voice +channelType.privateThread=Voice +channelType.unknown=Unknown checks.responseTemplate=**Error:** {0} diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index 47db791f4e..3126542353 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -1,23 +1,74 @@ -argumentParser.error.errorInArgument=**Error in argument** `{0}`**\:** {1} +argumentParser.error.errorInArgument=**Error in argument** `{0}`**:** {1} argumentParser.error.requiresOneValue=Argument `{0}` requires exactly 1 value, but {1} were provided. -argumentParser.error.invalidValue=Invalid value for argument `{0}` (which accepts\: {1}) +argumentParser.error.invalidValue=Invalid value for argument `{0}` (which accepts: {1}) argumentParser.error.notAllValid=Argument `{0}` was provided with {1} {1, plural, \=1 {value} other {values}}, but {2, plural, \=0 {none were valid} other {only {2} of them were valid }} {3} values. argumentParser.error.unknownConverterType=Unknown converter type provided\: `{0}` argumentParser.error.noFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}. argumentParser.error.someFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}, but only {1} could be filled. + +channelType.dm=DM +channelType.groupDm=Group DM +channelType.guildCategory=Category +channelType.guildNews=News +channelType.guildStageVoice=Stage +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=Voice +channelType.publicGuildThread=Voice +channelType.privateThread=Voice +channelType.unknown=Unknown + +checks.responseTemplate=**Error:** {0} + +checks.inChannel.failed=Must be in **{0}** +checks.notInChannel.failed=Must not be in **{0}** +checks.inCategory.failed=Must be in category: **{0}** +checks.notInCategory.failed=Must not be in category: **{0}** +checks.channelHigher.failed=Must be in a channel higher than **{0}** +checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel +checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel + +checks.anyGuild.failed=Must be in a server +checks.noGuild.failed=Must not be in a server +checks.inGuild.failed=Must be in server: **{0}** +checks.notInGuild.failed=Must not be in server: **{0}** + +checks.channelType.failed=Must be in a channel of type: **{0}** +checks.notChannelType.failed=Must not be in a channel of type: **{0}** + +checks.hasPermission.failed=Must have permission: **{0}** +checks.notHasPermission.failed=Must not have permission: **{0}** + +checks.isBot.failed=Must be a bot +checks.isNotBot.failed=Must not be a bot + +checks.isInThread.failed=Must be in a thread +checks.isNotInThread.failed=Must not be in a thread + +checks.hasRole.failed=Must have role: **{0}** +checks.notHasRole.failed=Must not have role: **{0}** +checks.topRoleEqual.failed=Must have top role: **{0}** +checks.topRoleNotEqual.failed=Must not have top role: **{0}** +checks.topRoleHigher.failed=Must have a top role higher than: **{0}** +checks.topRoleLower.failed=Must have a top role lower than: **{0}** +checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role +checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role + commands.defaultDescription=No description provided. -commands.error.missingBotPermissions=I don''t have the permissions I need to run that command\!\n\n**Missing permissions\:** {0} +commands.error.missingBotPermissions=I don't have the permissions I need to run that command\!\n\n**Missing permissions\:** {0} commands.error.user=Unfortunately, **an error occurred** during command processing. Please let a staff member know. -commands.error.user.sentry.message=Unfortunately, **an error occurred** during command processing. If you''d like to submit information on what you were doing when this error happened, please use the following command\: ```{0}feedback {1} ``` -commands.error.user.sentry.slash=Unfortunately, **an error occurred** during command processing. If you''d like to submit information on what you were doing when this error happened, please use the following command\: ```/feedback {0} ``` +commands.error.user.sentry.message=Unfortunately, **an error occurred** during command processing. If you'd like to submit information on what you were doing when this error happened, please use the following command\: ```{0}feedback {1} ``` +commands.error.user.sentry.slash=Unfortunately, **an error occurred** during command processing. If you'd like to submit information on what you were doing when this error happened, please use the following command\: ```/feedback {0} ``` converters.boolean.signatureType=yes/no converters.boolean.errorType=`yes` or `no` converters.channel.signatureType=channel converters.channel.error.missing=Unable to find channel\: {0} converters.channel.error.invalid=Value `{0}` is not a valid channel ID. -converters.color.error.unknown=Unknown colour\: `{0}` -converters.color.error.unknownOrFailed=Failed to parse colour\: `{0}` -converters.color.signatureType=colour +converters.color.error.unknown=Unknown color\: `{0}` +converters.color.error.unknownOrFailed=Failed to parse color\: `{0}` +converters.color.signatureType=color converters.decimal.signatureType=decimal converters.decimal.error.invalid=Value `{0}` is not a valid decimal number. converters.duration.error.signatureType=duration @@ -35,7 +86,7 @@ converters.guild.signatureType=server converters.guild.error.missing=Unable to find server\: `{0}` converters.number.signatureType=number converters.number.error.invalid.defaultBase=Value `{0}` is not a valid whole number. -converters.number.error.invalid.otherBase=Value `{0}` is not a valid whole number in {1, plural, \=2 {binary (base-2)} \=8 {octal (base-8)} \=10 {decimal (base-10)} \=16 {hexadecimal (base-16)} other {base-{1}} }. +converters.number.error.invalid.otherBase=Value `{0}` is not a valid whole number in {1, plural, =2 {binary (base-2)} =8 {octal (base-8)} =10 {decimal (base-10)} =16 {hexadecimal (base-16)} other {base-{1}} }. converters.member.signatureType=member converters.member.error.missing=Unable to find member\: {0} converters.member.error.invalid=Value `{0}` is not a valid member ID. @@ -54,7 +105,7 @@ converters.snowflake.signatureType=ID converters.snowflake.error.invalid=Value `{0}` is not a valid Discord ID. converters.string.signatureType=text converters.supportedLocale.signatureType=locale name/code -converters.supportedLocale.error.unknown=Unknown (or unsupported) locale\: `{0}` +converters.supportedLocale.error.unknown=Unknown (or unsupported) locale: `{0}` converters.union.error.unknownConverterType=Unknown converter type provided\: `{0}` converters.user.signatureType=user converters.user.error.missing=Unable to find user\: `{0}` @@ -71,7 +122,7 @@ extensions.help.commandDescription.error.argumentList=Failed to retrieve argumen extensions.help.paginator.title.command=Command\: {0} extensions.help.paginator.title.commands=Commands extensions.help.paginator.title.arguments=Command Arguments -extensions.help.paginator.footer={0} {0, plural, \=1 {command} other {commands}} available +extensions.help.paginator.footer={0} {0, plural, =1 {command} other {commands}} available extensions.help.paginator.noCommands=No commands found. extensions.help.error.missingCommandTitle=Command not found extensions.help.error.missingCommandDescription=Unable to find that command. This may be for one of several possible reasons\: \n\n**»** The command doesn't exist or failed to load\n**»** The command isn't available in this context\n**»** You don't have access to the command\n\nIf you feel that this is incorrect, please contact a member of staff. @@ -92,6 +143,7 @@ paginator.button.group.switch=Next Group paginator.button.less=Less paginator.footer.page=Page {0}/{1} paginator.footer.group=Group {0}/{1} + permission.addReactions=Add Reactions permission.administrator=Administrator permission.all=All Permissions @@ -130,6 +182,7 @@ permission.useVAD=Use Voice Activity permission.viewAuditLog=View Audit Log permission.viewChannel=View Channel permission.viewGuildInsights=View Server Insights + utils.message.useThisChannel=Please use {0} for this command. utils.message.commandNotAvailableInDm=This command is not available via private message. utils.colors.black=black,blck,blk From 85e5b536c88c70c8b72259e93b6a58e73ddfbfab Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 12 Sep 2021 14:39:41 +0100 Subject: [PATCH 052/131] [EN/EN_GB] Fix thread channel type strings --- .../main/resources/translations/kordex/strings.properties | 6 +++--- .../resources/translations/kordex/strings_en_GB.properties | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 3126542353..79988157d8 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -14,9 +14,9 @@ channelType.guildStageVoice=Stage channelType.guildStore=Store channelType.guildText=Text channelType.guildVoice=Voice -channelType.publicNewsThread=Voice -channelType.publicGuildThread=Voice -channelType.privateThread=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread channelType.unknown=Unknown checks.responseTemplate=**Error:** {0} diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index 3126542353..79988157d8 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -14,9 +14,9 @@ channelType.guildStageVoice=Stage channelType.guildStore=Store channelType.guildText=Text channelType.guildVoice=Voice -channelType.publicNewsThread=Voice -channelType.publicGuildThread=Voice -channelType.privateThread=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread channelType.unknown=Unknown checks.responseTemplate=**Error:** {0} From ffa3bf692682052fdc98d3f2e68fd74b7b1cd296 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 12 Sep 2021 14:54:35 +0100 Subject: [PATCH 053/131] Manually upload changes from Crowdin --- .../kord/extensions/i18n/SupportedLocales.kt | 7 + .../kordex/strings_de_DE.properties | 59 ++++- .../kordex/strings_fi_FI.properties | 57 +++++ .../kordex/strings_fr_FR.properties | 57 +++++ .../kordex/strings_pl_PL.properties | 57 +++++ .../kordex/strings_pt_PT.properties | 204 ++++++++++++++++++ .../kordex/strings_ru_RU.properties | 57 +++++ .../kordex/strings_zh_CN.properties | 57 +++++ 8 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt index c04d790d01..7e55b03ae1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt @@ -14,6 +14,7 @@ public object SupportedLocales { public val FINNISH: Locale = Locale("fi", "fi") public val FRENCH: Locale = Locale("fr", "fr") public val GERMAN: Locale = Locale("de", "de") + public val PORTUGUESE: Locale = Locale("pt", "pt") public val ALL_LOCALES: Map = mapOf( "中文" to CHINESE_SIMPLIFIED, @@ -46,5 +47,11 @@ public object SupportedLocales { "german" to GERMAN, "de" to GERMAN, "de_de" to GERMAN, + + "português" to PORTUGUESE, + "portugues" to PORTUGUESE, + "portuguese" to PORTUGUESE, + "pt" to PORTUGUESE, + "pt_pt" to PORTUGUESE, ) } diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties index 597c60fef2..cec95b893e 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties @@ -1,10 +1,61 @@ argumentParser.error.errorInArgument=**Fehler in Argument** `{0}`**\:** {1} argumentParser.error.requiresOneValue=Das Argument `{0}` benötigt genau einen Wert, aber {1} wurden angegeben. -argumentParser.error.invalidValue=Ungültiger Wert für das Argument `{0}` (akzeptiert {1}) +argumentParser.error.invalidValue=Ungültiger Wert für das Argument `{0}` (akzeptiert\: {1}) argumentParser.error.notAllValid=Das Argument `{0}` wurde mit {1} {1, plural, \=1 {Wert} other {Werten}} angegeben, aber {2, plural, \=0 {keine davon waren gültige {3}-Werte} \=1{nur {2} davon war ein gültiger {3}-Wert} other {nur {2} davon waren gültige {3}-Werte}}. argumentParser.error.unknownConverterType=Unbekannter Konvertertyp\: `{0}` argumentParser.error.noFilledArguments=Dieser Befehl hat {0} {0, plural, \=1 {erforderliches Argument} other {erforderliche Argumente}}. argumentParser.error.someFilledArguments=Dieser Befehl hat {0} {0, plural, \=1 {erforderliches Argument} other {erforderliche Argumente}}, aber nur {1} {1, plural, \=1 {konnte} other {konnten}} ausgefüllt werden. + +channelType.dm=DM +channelType.groupDm=Group DM +channelType.guildCategory=Category +channelType.guildNews=News +channelType.guildStageVoice=Stage +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Unknown + +checks.responseTemplate=**Fehler\:** {0} + +checks.inChannel.failed=Must be in **{0}** +checks.notInChannel.failed=Must not be in **{0}** +checks.inCategory.failed=Must be in category\: **{0}** +checks.notInCategory.failed=Must not be in category\: **{0}** +checks.channelHigher.failed=Must be in a channel higher than **{0}** +checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel +checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel + +checks.anyGuild.failed=Must be in a server +checks.noGuild.failed=Must not be in a server +checks.inGuild.failed=Must be in server\: **{0}** +checks.notInGuild.failed=Must not be in server\: **{0}** + +checks.channelType.failed=Must be in a channel of type\: **{0}** +checks.notChannelType.failed=Must not be in a channel of type\: **{0}** + +checks.hasPermission.failed=Must have permission\: **{0}** +checks.notHasPermission.failed=Must not have permission\: **{0}** + +checks.isBot.failed=Must be a bot +checks.isNotBot.failed=Must not be a bot + +checks.isInThread.failed=Must be in a thread +checks.isNotInThread.failed=Must not be in a thread + +checks.hasRole.failed=Must have role\: **{0}** +checks.notHasRole.failed=Must not have role\: **{0}** +checks.topRoleEqual.failed=Must have top role\: **{0}** +checks.topRoleNotEqual.failed=Must not have top role\: **{0}** +checks.topRoleHigher.failed=Must have a top role higher than\: **{0}** +checks.topRoleLower.failed=Must have a top role lower than\: **{0}** +checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role +checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role + commands.defaultDescription=Keine Beschreibung verfügbar. commands.error.missingBotPermissions=Ich habe nicht die Berechtigungen, die ich brauche, um diesen Befehl auszuführen\!\n\n**Fehlende Berechtigungen\:** {0} commands.error.user=Leider **ist ein Fehler passiert** während der Befehl verarbeitet wurde. Bitte informiere deine Server-Administration. @@ -92,6 +143,7 @@ paginator.button.group.switch=Nächste Gruppe paginator.button.less=Weniger paginator.footer.page=Seite {0}/{1} paginator.footer.group=Gruppe {0}/{1} + permission.addReactions=Reaktionen hinzufügen permission.administrator=Administrator permission.all=Alle Berechtigungen @@ -100,6 +152,8 @@ permission.banMembers=Mitglieder bannen permission.changeNickname=Nickname ändern permission.connect=Verbinden (Sprachkanal) permission.createInstantInvite=Einladung erstellen +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Ausgabe von Mitgliedern deaktivieren permission.embedLinks=Links einbetten permission.kickMembers=Mitglieder kicken @@ -109,6 +163,7 @@ permission.manageGuild=Server verwalten permission.manageMessages=Nachrichten verwalten permission.manageNicknames=Nicknames verwalten permission.manageRoles=Rollen verwalten +permission.manageThreads=Server-Einblicke anzeigen permission.manageWebhooks=WebHooks verwalten permission.mentionEveryone=Erwähne @everyone permission.moveMembers=Mitglieder verschieben @@ -117,6 +172,7 @@ permission.prioritySpeaker=Very Important Speaker permission.readMessageHistory=Nachrichtenverlauf anzeigen permission.requestToSpeak=Redeanfrage permission.sendMessages=Nachrichten senden +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Text-zu-Sprache-Nachrichten senden permission.speak=Sprechen (Sprachkanal) permission.stream=Video @@ -126,6 +182,7 @@ permission.useVAD=Sprachaktivierung verwenden permission.viewAuditLog=Audit-Log einsehen permission.viewChannel=Kanäle ansehen permission.viewGuildInsights=Server-Einblicke anzeigen + utils.message.useThisChannel=Verwende für diesen Befehl bitte {0}. utils.message.commandNotAvailableInDm=Dieser Befehl ist nicht via Direktnachricht verfügbar. utils.colors.black=schwarz,schwrz diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_fi_FI.properties b/kord-extensions/src/main/resources/translations/kordex/strings_fi_FI.properties index f2e74f56db..2dc47a4d46 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_fi_FI.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_fi_FI.properties @@ -5,6 +5,57 @@ argumentParser.error.notAllValid=Argumentille `{0}` annettiin {1} {1, plural, \= argumentParser.error.unknownConverterType=Tuntematon muunnintyyppi annettu\: `{0}` argumentParser.error.noFilledArguments=Tällä komennolla on {0} {0, plural, \=1 {vaadittu argumentti} other {vaadittua argumenttia}}. argumentParser.error.someFilledArguments=Tällä komennolla on {0} {0, plural, \=1 {vaadittu argumentti} other {vaadittua argumenttia}}, mutta vain {1} voitiin täyttää. + +channelType.dm=DM +channelType.groupDm=Group DM +channelType.guildCategory=Kategoria +channelType.guildNews=Uutiset +channelType.guildStageVoice=Esitys +channelType.guildStore=Kauppa +channelType.guildText=Teksti +channelType.guildVoice=Ääni +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Tuntematon + +checks.responseTemplate=**Virhe\:** {0} + +checks.inChannel.failed=Pitää sisältyä kanavaan **{0}** +checks.notInChannel.failed=Ei pidä sisältyä kanavaan **{0}** +checks.inCategory.failed=Pitää olla kategoriassa\: **{0}** +checks.notInCategory.failed=Ei pidä olla kategoriassa\: **{0}** +checks.channelHigher.failed=Pitää olla korkeammalla kanavalla kuin **{0}** +checks.channelLower.failed=Ei pidä olla matalammala kanavalla kuin **{0}** +checks.channelHigherOrEqual.failed=Pitää olla kanavalla **{0}** tai korkeampi +checks.channelLowerOrEqual.failed=Pitää olla kanavalla **{0}**, tai matalampi + +checks.anyGuild.failed=Pitää olla palvelimella +checks.noGuild.failed=Ei pidä olla palvelimella +checks.inGuild.failed=Pitää olla palvelimella **{0}** +checks.notInGuild.failed=Ei pidä olla palvelimella **{0}** + +checks.channelType.failed=Pitää olla kanavalla tyypin **{0}** +checks.notChannelType.failed=Ei pidä olla kanavalla tyypin **{0}** + +checks.hasPermission.failed=Pitää olla lupa **{0}** +checks.notHasPermission.failed=Ei pidä olla lupa **{0}** + +checks.isBot.failed=Pitää olla botti +checks.isNotBot.failed=Ei pidä olla botti + +checks.isInThread.failed=Must be in a thread +checks.isNotInThread.failed=Must not be in a thread + +checks.hasRole.failed=Pitää olla rooli **{0}** +checks.notHasRole.failed=Ei pidä olla roolia **{0}** +checks.topRoleEqual.failed=Pitää olla päärooli **{0}** +checks.topRoleNotEqual.failed=Ei pidä olla päärooli **{0}** +checks.topRoleHigher.failed=Pitää olla päärooli korkeampi kuin **{0}** +checks.topRoleLower.failed=Ei pidä olla päärooli korkeampi kuin **{0}** +checks.topRoleHigherOrEqual.failed=Pitää olla päärooli **{0}** tai korkeampi +checks.topRoleLowerOrEqual.failed=Pitää olla päärooli **{0}** tai matalampi + commands.defaultDescription=Kuvausta ei annettu. commands.error.missingBotPermissions=Minulla ei ole oikeuksia suorittaa tuota komentoa\!\n\n**Puuttuvat oikeudet\:** {0} commands.error.user=Valitettavasti **virhe tapahtui** komennon suorittamisen aikana. Raportoi tämä henkilökunnalle. @@ -92,6 +143,7 @@ paginator.button.group.switch=Seuraava Ryhmä paginator.button.less=Vähemmän paginator.footer.page=Sivu {0}/{1} paginator.footer.group=Ryhmä {0}/{1} + permission.addReactions=Lisää reaktioita permission.administrator=Järjestelmänvalvoja permission.all=Kaikki oikeudet @@ -100,6 +152,8 @@ permission.banMembers=Anna porttikieltoja jäsenille permission.changeNickname=Muuta nimimerkkiä permission.connect=Yhdistä permission.createInstantInvite=Kutsun luonti +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Poista ääniä jäseniltä permission.embedLinks=Upota linkkejä permission.kickMembers=Erota jäseniä @@ -109,6 +163,7 @@ permission.manageGuild=Hallinnoi palvelinta permission.manageMessages=Hallinnoi viestejä permission.manageNicknames=Hallinnoi nimimerkkejä permission.manageRoles=Hallinnoi rooleja +permission.manageThreads=Näytä palvelinanalyysit permission.manageWebhooks=Hallinnoi webhookeja permission.mentionEveryone=@everyone ja @here sekä kaikki roolit mainittavissa permission.moveMembers=Siirrä jäseniä @@ -117,6 +172,7 @@ permission.prioritySpeaker=Ensisijainen puhuja permission.readMessageHistory=Lue viestihistoriaa permission.requestToSpeak=Pyydä puhevuoro permission.sendMessages=Lähetä viestejä +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Lähetä tekstistä puheeksi -viestejä permission.speak=Puhu permission.stream=Video @@ -126,6 +182,7 @@ permission.useVAD=Käytä puheentunnistusta permission.viewAuditLog=Katso valvontalokia permission.viewChannel=Näe kanava permission.viewGuildInsights=Näytä palvelinanalyysit + utils.message.useThisChannel=Käytä tätä komentoa kanavassa {0}. utils.message.commandNotAvailableInDm=Tätä komentoa ei voi käyttää yksityisviestillä. utils.colors.black=musta diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties index 1feeb8696c..47bae4d697 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties @@ -5,6 +5,57 @@ argumentParser.error.notAllValid=Le paramètre `{0}` a été saisi avec {1} {1, argumentParser.error.unknownConverterType=Type de convertisseur inconnu \: {0}` argumentParser.error.noFilledArguments=Cette commande a {0} {0, plural, \=1 {paramètre} other {paramètres}} requis. argumentParser.error.someFilledArguments=Cette commande a {0} {0, plural, one {} \=1 {paramètre} other {paramètres}} requis, mais seulement {1} ont été rempli. + +channelType.dm=MP +channelType.groupDm=Groupe MP +channelType.guildCategory=Catégorie +channelType.guildNews=Actualités +channelType.guildStageVoice=Stage +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Unknown + +checks.responseTemplate=**Error\:** {0} + +checks.inChannel.failed=Must be in **{0}** +checks.notInChannel.failed=Must not be in **{0}** +checks.inCategory.failed=Must be in category\: **{0}** +checks.notInCategory.failed=Must not be in category\: **{0}** +checks.channelHigher.failed=Must be in a channel higher than **{0}** +checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel +checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel + +checks.anyGuild.failed=Must be in a server +checks.noGuild.failed=Must not be in a server +checks.inGuild.failed=Must be in server\: **{0}** +checks.notInGuild.failed=Must not be in server\: **{0}** + +checks.channelType.failed=Must be in a channel of type\: **{0}** +checks.notChannelType.failed=Must not be in a channel of type\: **{0}** + +checks.hasPermission.failed=Must have permission\: **{0}** +checks.notHasPermission.failed=Must not have permission\: **{0}** + +checks.isBot.failed=Must be a bot +checks.isNotBot.failed=Must not be a bot + +checks.isInThread.failed=Must be in a thread +checks.isNotInThread.failed=Must not be in a thread + +checks.hasRole.failed=Must have role\: **{0}** +checks.notHasRole.failed=Must not have role\: **{0}** +checks.topRoleEqual.failed=Must have top role\: **{0}** +checks.topRoleNotEqual.failed=Must not have top role\: **{0}** +checks.topRoleHigher.failed=Must have a top role higher than\: **{0}** +checks.topRoleLower.failed=Must have a top role lower than\: **{0}** +checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role +checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role + commands.defaultDescription=Aucune description fournie. commands.error.missingBotPermissions=Je n’ai pas la permission d’exécuter cette commande \!\n\n**Permissions manquantes \:** {0} commands.error.user=Malheureusement, **une erreur s’est produite** lors du traitement de la commande. Veuillez en faire part à un membre de l'équipe. @@ -92,6 +143,7 @@ paginator.button.group.switch=Groupe suivant paginator.button.less=Moins paginator.footer.page=Page {0}/{1} paginator.footer.group=Groupe {0}/{1} + permission.addReactions=Ajouter des réactions permission.administrator=Administrateur permission.all=Toutes les autorisations @@ -100,6 +152,8 @@ permission.banMembers=Bannir des membres permission.changeNickname=Changer le surnom permission.connect=Connecter (Voix) permission.createInstantInvite=Créer une invitation +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Mettre en sourdine des membres permission.embedLinks=Intégrer des liens permission.kickMembers=Exclure des membres @@ -109,6 +163,7 @@ permission.manageGuild=Gérer le serveur permission.manageMessages=Gérer les messages permission.manageNicknames=Gérer les surnoms permission.manageRoles=Gérer les rôles +permission.manageThreads=Voir les analyses de serveur permission.manageWebhooks=Gérer les Webhooks permission.mentionEveryone=Mentionner tout le monde permission.moveMembers=Déplacer les membres @@ -117,6 +172,7 @@ permission.prioritySpeaker=Voix prioritaire permission.readMessageHistory=Lire l'historique des messages permission.requestToSpeak=Demander pour parler permission.sendMessages=Envoyer des messages +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Envoyer des messages TTS permission.speak=Parler (Voix) permission.stream=Vidéo @@ -126,6 +182,7 @@ permission.useVAD=Utiliser l'activité vocale permission.viewAuditLog=Afficher le journal d'informations permission.viewChannel=Voir le salon permission.viewGuildInsights=Voir les analyses de serveur + utils.message.useThisChannel=Veuillez utiliser {0} pour cette commande. utils.message.commandNotAvailableInDm=Cette commande n'est pas disponible dans les messages privés. utils.colors.black=noires,noire,noirs,noir diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_pl_PL.properties b/kord-extensions/src/main/resources/translations/kordex/strings_pl_PL.properties index b0eb958824..89aad38dd2 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_pl_PL.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_pl_PL.properties @@ -5,6 +5,57 @@ argumentParser.error.notAllValid=Argument `{0}` został podany z {1} {1, plural, argumentParser.error.unknownConverterType=Nieznany typ konwertera\: `{0}` argumentParser.error.noFilledArguments=To polecenie ma {0} {0, plural, \=1 {wymagany argument} \=2 {wymagane argumenty} \=3 {wymagane argumenty} \=4 {wymagane argumenty} other {wymaganych argumentów}}. argumentParser.error.someFilledArguments=To polecenie ma {0} {0, plural, one {wymagany argument} few {wymagane argumenty} other {wymaganych argumentów}}, ale tylko {1} {1, plural, one {mógł zostać wypełniony} few {mogły zostać wypełnione} other {mogło zostać wypełnionych}}. + +channelType.dm=PW +channelType.groupDm=Grupa +channelType.guildCategory=Kategoria +channelType.guildNews=Ogłoszenia +channelType.guildStageVoice=Podium +channelType.guildStore=Sklep +channelType.guildText=Tekstowy +channelType.guildVoice=Głosowy +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Nieznany + +checks.responseTemplate=**Błąd\:** {0} + +checks.inChannel.failed=Musi być w **{0}** +checks.notInChannel.failed=Nie może być w **{0}** +checks.inCategory.failed=Musi być w kategorii\: **{0}** +checks.notInCategory.failed=Nie może być w kategorii\: **{0}** +checks.channelHigher.failed=Musi być na kanale wyższym niż **{0}** +checks.channelLower.failed=Nie może być na kanale niższym niż **{0}** +checks.channelHigherOrEqual.failed=Musi być na **{0}**, lub wyższym kanale +checks.channelLowerOrEqual.failed=Musi być na **{0}**, lub niższym kanale + +checks.anyGuild.failed=Musi być w serwerze +checks.noGuild.failed=Nie może być w serwerze +checks.inGuild.failed=Musi być na serwerze\: **{0}** +checks.notInGuild.failed=Nie może być w serwerze\: **{0}** + +checks.channelType.failed=Musi być na kanale typu\: **{0}** +checks.notChannelType.failed=Nie może być na kanale typu\: **{0}** + +checks.hasPermission.failed=Musi mieć uprawnienia\: **{0}** +checks.notHasPermission.failed=Nie może mieć uprawnień\: **{0}** + +checks.isBot.failed=Musi być botem +checks.isNotBot.failed=Nie może być botem + +checks.isInThread.failed=Musi być w wątku +checks.isNotInThread.failed=Nie może być w wątku + +checks.hasRole.failed=Musi mieć rolę\: **{0}** +checks.notHasRole.failed=Nie wolno mieć roli\: **{0}** +checks.topRoleEqual.failed=Najwyższa wymagana rola\: **{0}** +checks.topRoleNotEqual.failed=Najwyższa możliwa rola\: **{0}** +checks.topRoleHigher.failed=Musi mieć najwyższą rolę większą niż\: **{0}** +checks.topRoleLower.failed=Musi mieć najniższą rolę mniejszą niż\: **{0}** +checks.topRoleHigherOrEqual.failed=Musi mieć najwyższą rolę **{0}**, lub wyższą +checks.topRoleLowerOrEqual.failed=Musi mieć najwyższą rolę **{0}**, lub niższą górną rolę + commands.defaultDescription=Nie podano opisu. commands.error.missingBotPermissions=Nie mam uprawnień, których potrzebuję do wykonania tego polecenia\!\n\n**Brakujące uprawnienia\:** {0} commands.error.user=Niestety, **wystąpił błąd** podczas przetwarzania polecenia. Proszę poinformować członka zespołu. @@ -92,6 +143,7 @@ paginator.button.group.switch=Następna Grupa paginator.button.less=Mniej paginator.footer.page=Strona {0}/{1} paginator.footer.group=Grupa {0}/{1} + permission.addReactions=Dodawanie reakcji permission.administrator=Administrator permission.all=Wszystkie uprawnienia @@ -100,6 +152,8 @@ permission.banMembers=Banowanie członków permission.changeNickname=Zmiana pseudonimu permission.connect=Połącz permission.createInstantInvite=Tworzenie zaproszenia +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=Wyciszanie członków permission.embedLinks=Wyświetlanie podglądu linku permission.kickMembers=Wyrzucanie członków @@ -109,6 +163,7 @@ permission.manageGuild=Zarządzanie serwerem permission.manageMessages=Zarządzanie wiadomościami permission.manageNicknames=Zarządzanie pseudonimami permission.manageRoles=Zarządzanie rolami +permission.manageThreads=Pokaż przegląd serwera permission.manageWebhooks=Zarządzanie webhookami permission.mentionEveryone=Zamieść wzmiankę @everyone, @here oraz wszystkie role permission.moveMembers=Przenoszenie członków @@ -117,6 +172,7 @@ permission.prioritySpeaker=Priorytetowy rozmówca permission.readMessageHistory=Czytanie historii czatu permission.requestToSpeak=Poproś o głos permission.sendMessages=Wysyłanie wiadomości +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=Wysyłanie wiadomości TTS permission.speak=Mówienie permission.stream=Wideo @@ -126,6 +182,7 @@ permission.useVAD=Używanie Aktywności Głosowej permission.viewAuditLog=Wyświetlanie dziennika zdarzeń permission.viewChannel=Wyświetlanie kanału permission.viewGuildInsights=Pokaż przegląd serwera + utils.message.useThisChannel=Proszę użyć {0} dla tego polecenia. utils.message.commandNotAvailableInDm=To polecenie nie jest dostępne w prywatnych wiadomościach. utils.colors.black=czarny,blck,blk diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties new file mode 100644 index 0000000000..c232eb78c2 --- /dev/null +++ b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties @@ -0,0 +1,204 @@ +argumentParser.error.errorInArgument=**Erro no argumento** `{0}`**\:** {1} +argumentParser.error.requiresOneValue=O argumento `{0}` necessita de exatamente 1 valor, mas {1} foram fornecidos. +argumentParser.error.invalidValue=Invalid value for argument `{0}` (which accepts\: {1}) +argumentParser.error.notAllValid=O argumento `{0}` recebeu {1} {1, plural, \=1 {valor} other {valores}}, porém {2, plural, \=0 {nenhum deles foram válores válidos.} other { somente {2} deles foram válores válidos.}} {3} valores. +argumentParser.error.unknownConverterType=Um conversor de tipo desconhecido foi fornecido\: `{0}` +argumentParser.error.noFilledArguments=Esse comando necessita de {0} {0, plural, one {} \=1 {argumento} other {argumentos}}. +argumentParser.error.someFilledArguments=Esse comando necessita de {0} {0, plural, one {} \=1 {argumento} other {argumentos}}, porém somente {1} foi completado(s). + +channelType.dm=DM +channelType.groupDm=Group DM +channelType.guildCategory=Category +channelType.guildNews=News +channelType.guildStageVoice=Stage +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Unknown + +checks.responseTemplate=**Error\:** {0} + +checks.inChannel.failed=Must be in **{0}** +checks.notInChannel.failed=Must not be in **{0}** +checks.inCategory.failed=Must be in category\: **{0}** +checks.notInCategory.failed=Must not be in category\: **{0}** +checks.channelHigher.failed=Must be in a channel higher than **{0}** +checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel +checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel + +checks.anyGuild.failed=Must be in a server +checks.noGuild.failed=Must not be in a server +checks.inGuild.failed=Must be in server\: **{0}** +checks.notInGuild.failed=Must not be in server\: **{0}** + +checks.channelType.failed=Must be in a channel of type\: **{0}** +checks.notChannelType.failed=Must not be in a channel of type\: **{0}** + +checks.hasPermission.failed=Must have permission\: **{0}** +checks.notHasPermission.failed=Must not have permission\: **{0}** + +checks.isBot.failed=Must be a bot +checks.isNotBot.failed=Must not be a bot + +checks.isInThread.failed=Must be in a thread +checks.isNotInThread.failed=Must not be in a thread + +checks.hasRole.failed=Must have role\: **{0}** +checks.notHasRole.failed=Must not have role\: **{0}** +checks.topRoleEqual.failed=Must have top role\: **{0}** +checks.topRoleNotEqual.failed=Must not have top role\: **{0}** +checks.topRoleHigher.failed=Must have a top role higher than\: **{0}** +checks.topRoleLower.failed=Must have a top role lower than\: **{0}** +checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role +checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role + +commands.defaultDescription=Nenhuma descrição fornecida. +commands.error.missingBotPermissions=Eu não tenho as permissões necessárias para executar esse comando\!\n\n**Permissões necessárias\:** {0} +commands.error.user=Infelizmente, **um erro ocorreu** durante o processamento do comando. Por favor, avise um moderador. +commands.error.user.sentry.message=Infelizmente, **ocorreu um erro** durante o processamento do comando. Se você gostaria de enviar informações sobre o que estava fazendo quando este erro aconteceu, por favor use o seguinte comando\: ```{0}feedback {1} ``` +commands.error.user.sentry.slash=Infelizmente, **ocorreu um erro** durante o processamento do comando. Se você gostaria de enviar informações sobre o que estava fazendo quando este erro aconteceu, por favor use o seguinte comando\: ```{0}feedback {1} ``` +converters.boolean.signatureType=sim/não +converters.boolean.errorType=`sim` ou `não` +converters.channel.signatureType=canal +converters.channel.error.missing=Não foi possível encontrar o canal\: {0} +converters.channel.error.invalid=`{0}` não é uma ID de canal válida. +converters.color.error.unknown=Unknown color\: `{0}` +converters.color.error.unknownOrFailed=Failed to parse color\: `{0}` +converters.color.signatureType=cor +converters.decimal.signatureType=decimal +converters.decimal.error.invalid=`{0}` não é um número decimal válido. +converters.duration.error.signatureType=duração +converters.duration.error.badUnitPairs=Você deve fornecer o mesmo número de unidades e valores. Números planos não são suportados. +converters.duration.error.invalidUnit=Uma unidade de tempo inválida foi especificada\: `{0}` +converters.duration.error.negativeUnsupported=Valores negativos não são suportados. +converters.duration.error.positiveOnly=É necessária uma unidade de tempo positiva. +converters.duration.help=__Como usar as unidades de tempo__\n\nUnidades de tempo são especificadas em pares de quantidades e unidades - por exemplo, `12d` são 12 dias.\nUnidades de tempo podem ser compostas (seguindo abreviações de unidades anglo-saxônicas) - por exemplo, `2w 3d` seria 2 semanas (weeks, em inglês) e 3 dias.\n\nAs seguintes unidades são suportadas\:\n\n**Segundos\:** `s`, `seg`, `segundo`, `segundos`\n**Minutos\:** `m`, `mi`, `min`, `minuto`, `minutos`\n**Horas\:** `h`, `hora`, `horas`\n**Dias\:** `d`, `dia`, `dias`\n**Semanas\:** `s`, `semana`, `semanas`\n**Meses\:** `me`, `mês`, `meses`\n**Anos\:** `a`, `ano`, `anos` +converters.email.signatureType=email +converters.email.error.invalid=Endereço de e-mail inválido especificado\: `{0}` +converters.emoji.signatureType=emoji do servidor +converters.emoji.error.missing=Não foi possível encontrar o emoji\: `{0}` +converters.emoji.error.invalid=`{0}` não é um ID de emoji válido. +converters.guild.signatureType=servidor +converters.guild.error.missing=Não foi possível encontrar o servidor\: `{0}` +converters.number.signatureType=número +converters.number.error.invalid.defaultBase=`{0}` não é um número inteiro válido. +converters.number.error.invalid.otherBase=O valor `{0}` não é um número inteiro válido em {1, plural, one {} \=2 {binário (base 2)} \=8 {octal (base 8)} \=10 {decimal (base 10)} \=16 {hexadecimal (base 16)} other {base {1}} }. +converters.member.signatureType=membro +converters.member.error.missing=Não foi possível encontrar o membro\: {0} +converters.member.error.invalid=`{0}` não é uma ID de membro válida. +converters.message.signatureType=mensagem +converters.message.error.invalidUrl=URL de mensagem inválida fornecida\: <{0}> +converters.message.error.invalidGuildId=`{0}` não é uma ID de servidor válida. +converters.message.error.invalidChannelId=`{0}` não é uma ID de canal válida. +converters.message.error.invalidMessageId=`{0}` não é uma ID de mensagem válida. +converters.message.error.missing=Não foi possível encontrar a mensagem\: `{0}` +converters.regex.signatureType.singular=regex +converters.regex.signatureType.plural=regexes +converters.role.signatureType=cargo +converters.role.error.missing=Não foi possível encontrar o cargo\: `{0}` +converters.role.error.invalid=`{0}` não é uma ID de cargo válida. +converters.snowflake.signatureType=ID +converters.snowflake.error.invalid=Valor `{0}` não é um ID do Discord válido. +converters.string.signatureType=texto +converters.supportedLocale.signatureType=nome/código da localidade +converters.supportedLocale.error.unknown=Local desconhecido (ou não suportado)\: `{0}` +converters.union.error.unknownConverterType=Um conversor de tipo desconhecido foi fornecido\: `{0}` +converters.user.signatureType=usuário +converters.user.error.missing=Não foi possível encontrar o usuário\: `{0}` +converters.user.error.invalid=`{0}` não é uma ID de usuário válida. +extensions.help.commandName=ajuda +extensions.help.commandAliases=a +extensions.help.commandDescription=Obtenha ajuda.\n\nEspecifique o nome de um comando para obter ajuda para esse comando específico. Os subcomandos também podem ser especificados da mesma forma que você os executa. +extensions.help.commandArguments.command=Comando para obter ajuda +extensions.help.commandDescription.aliases=**Aliases\:** +extensions.help.commandDescription.subCommands=**Subcomandos\:** +extensions.help.commandDescription.requiredBotPermissions=**Permissões requeridas pelo bot\:** +extensions.help.commandDescription.noArguments=Sem argumentos. +extensions.help.commandDescription.error.argumentList=Falha ao recuperar a lista de argumentos devido a um erro. +extensions.help.paginator.title.command=Comando\: {0} +extensions.help.paginator.title.commands=Comandos +extensions.help.paginator.title.arguments=Argumentos de Comando +extensions.help.paginator.footer={0} {0, plural, one {} \=1 {comando} other {comandos}} em disposição +extensions.help.paginator.noCommands=Nenhum comando encontrado. +extensions.help.error.missingCommandTitle=Comando não encontrado +extensions.help.error.missingCommandDescription=Não foi possível encontrar esse comando. Isto pode ser por uma das várias razões possíveis\: \n\n**»** O comando não existe ou não carregou corretamente\n**»** O comando não está disponível neste contexto\n**»** Você não tem acesso ao comando\n\nSe você acha que isso está incorreto, por favor, entre em contato com um moderador. +extensions.sentry.arguments.id=ID de evento sentinela +extensions.sentry.arguments.feedback=Feedback para enviar aos desenvolvedores +extensions.sentry.converter.sentryId.signatureType=UUID +extensions.sentry.converter.error.invalid=ID inválido de evento de sentinela especificado\: `{0}` +extensions.sentry.commandName=feedback +extensions.sentry.commandAliases=feedback-sentinela +extensions.sentry.commandDescription.short=Forneça feedback sobre o que você estava fazendo quando ocorreu um erro. +extensions.sentry.commandDescription.long=Se você recebeu um ID de sentinela pelo bot, você pode enviar comentários sobre para que estava usando este comando.\n\nSeu feedback idealmente deve incluir uma descrição do que você estava fazendo quando o erro ocorreu e o que você esperava que acontecesse. Mas o texto do seu feedback depende de você.\n\n**Nota\:** O feedback é inteiramente opcional, e você não deve se sentir obrigado a enviar feedback se você não quiser - se você recebeu um ID de evento, o erro já foi enviado\! +extensions.sentry.error.invalidId=O ID sentinela que você forneceu não existe ou não está aguardando feedback. +extensions.sentry.thanks=Obrigado pelo seu feedback - vamos usá-lo para melhorar o nosso bot e corrigir o erro que você encontrou\! +paginator.button.delete=Apagar +paginator.button.done=OK +paginator.button.more=Mais +paginator.button.group.switch=Próximo grupo +paginator.button.less=Menos +paginator.footer.page=Página {0}/{1} +paginator.footer.group=Grupo {0}/{1} + +permission.addReactions=Adicionar reações +permission.administrator=Administrador +permission.all=Todas as permissões +permission.attachFiles=Anexar arquivos +permission.banMembers=Banir Membros +permission.changeNickname=Alterar apelido +permission.connect=Conectar (voz) +permission.createInstantInvite=Criar convites +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads +permission.deafenMembers=Ensurdecer Membros +permission.embedLinks=Incorporar Links +permission.kickMembers=Expulsar membros +permission.manageChannels=Gerenciar canais +permission.manageEmojis=Gerenciar emojis +permission.manageGuild=Gerenciar servidor +permission.manageMessages=Gerenciar Mensagens +permission.manageNicknames=Gerenciar apelidos +permission.manageRoles=Gerenciar Cargos +permission.manageThreads=Ver estatísticas do servidor +permission.manageWebhooks=Gerenciar webhooks +permission.mentionEveryone=Mencionar Todos +permission.moveMembers=Mover membros +permission.muteMembers=Silenciar membros +permission.prioritySpeaker=Falante Prioritário +permission.readMessageHistory=Ver Histórico de Mensagens +permission.requestToSpeak=Pedido de palavra +permission.sendMessages=Enviar mensagens +permission.sendMessagesInThreads=Send Messages In Threads +permission.sendTTSMessages=Enviar mensagens em TTS +permission.speak=Falar (voz) +permission.stream=Vídeo +permission.useExternalEmojis=Usar emojis externos +permission.useSlashCommands=Usar comandos barra (\\) +permission.useVAD=Usar detecção de voz +permission.viewAuditLog=Visualizar registro de auditoria +permission.viewChannel=Ver Canal +permission.viewGuildInsights=Ver estatísticas do servidor + +utils.message.useThisChannel=Por favor, use {0} para este comando. +utils.message.commandNotAvailableInDm=Este comando não está disponível através de mensagens privadas. +utils.colors.black=preto,pt +utils.colors.blurple=desfoque,roxo,rx +utils.colors.fuchsia=fuchsia,rosa,rs +utils.colors.green=verde,vd +utils.colors.red=vermelho,vm +utils.colors.white=branco,br +utils.colors.yellow=amarelo,am +utils.durations.ignoredWords=e +utils.units.year=a, ano, anos +utils.units.month=me, mes, meses +utils.units.week=s, semana, semanas +utils.units.day=d, dia, dias +utils.units.hour=h, hora, horas +utils.units.minute=m, mi, min, mins, minuto, minutos +utils.units.second=s, sec, secs, second, seconds +utils.string.false=0, n, não, f, falso +utils.string.true=1, s, sim, t, verdadeiro diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties index a859e689b7..eca24e8768 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties @@ -5,6 +5,57 @@ argumentParser.error.notAllValid=Для аргумента `{0}` дано {1} {1 argumentParser.error.unknownConverterType=Указан неизвестный тип конвертера\: `{0}` argumentParser.error.noFilledArguments=У команды {0} {0, plural, one {обязательный аргумент} few {обязательных аргумента} other {обязательных аргументов}}. argumentParser.error.someFilledArguments=У команды {0} {0, plural, one {обязательный аргумент} few {обязательных аргумента} other {обязательных аргументов}}, но удалось заполнить только {1}. + +channelType.dm=ЛС +channelType.groupDm=Групповые ЛС +channelType.guildCategory=Категория +channelType.guildNews=Новости +channelType.guildStageVoice=Трибуна +channelType.guildStore=Store +channelType.guildText=Text +channelType.guildVoice=Voice +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=Неизвестный + +checks.responseTemplate=**Ошибка\:** {0} + +checks.inChannel.failed=Must be in **{0}** +checks.notInChannel.failed=Must not be in **{0}** +checks.inCategory.failed=Разрешено только в категории\: **{0}** +checks.notInCategory.failed=Не разрешено в категории\: **{0}** +checks.channelHigher.failed=Must be in a channel higher than **{0}** +checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel +checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel + +checks.anyGuild.failed=Разрешено только на сервере +checks.noGuild.failed=Не разрешено на сервере +checks.inGuild.failed=Разрешено только на сервере\: **{0}** +checks.notInGuild.failed=Не разрешено на сервере\: **{0}** + +checks.channelType.failed=Тип канала должен быть **{0}** +checks.notChannelType.failed=Тип канала не должен быть **{0}** + +checks.hasPermission.failed=Должно быть разрешение\: **{0}** +checks.notHasPermission.failed=Не должно быть разрешения\: **{0}** + +checks.isBot.failed=Разрешено только для бота +checks.isNotBot.failed=Не разрешено для бота + +checks.isInThread.failed=Разрешено только в ветке +checks.isNotInThread.failed=Не разрешено в ветке + +checks.hasRole.failed=Должна быть роль\: **{0}** +checks.notHasRole.failed=Не дложно быть роли\: **{0}** +checks.topRoleEqual.failed=Верхняя роль должна быть\: **{0}** +checks.topRoleNotEqual.failed=Верхняя роль не должна быть\: **{0}** +checks.topRoleHigher.failed=Верхняя роль должна быть выше, чем **{0}** +checks.topRoleLower.failed=Верхняя роль должна быть ниже, чем **{0}** +checks.topRoleHigherOrEqual.failed=Верхняя роль должна быть **{0}** или выше +checks.topRoleLowerOrEqual.failed=Верхняя роль должна быть **{0}** или ниже + commands.defaultDescription=Описание отсутствует. commands.error.missingBotPermissions=У меня не хватает разрешений для выполнения этой команды\!\n\n**Требуются разрешения\:** {0} commands.error.user=К сожалению, во время исполнения команды **произошла ошибка**. Сообщите об этом персоналу сервера. @@ -92,6 +143,7 @@ paginator.button.group.switch=Следующая группа paginator.button.less=Меньше paginator.footer.page=Страница {0}/{1} paginator.footer.group=Группа {0}/{1} + permission.addReactions=Добавлять реакции permission.administrator=Администратор permission.all=Все разрешения @@ -100,6 +152,8 @@ permission.banMembers=Банить участников permission.changeNickname=Изменить никнейм permission.connect=Подключаться (в голосовой канал) permission.createInstantInvite=Создание приглашения +permission.createPrivateThreads=Создавать публичные ветки +permission.createPublicThreads=Создавать приватные ветки permission.deafenMembers=Отключать участникам звук permission.embedLinks=Встраивать ссылки permission.kickMembers=Выгонять участников @@ -109,6 +163,7 @@ permission.manageGuild=Управлять сервером permission.manageMessages=Управлять сообщениями permission.manageNicknames=Управлять никнеймами permission.manageRoles=Управлять ролями +permission.manageThreads=Просматривать аналитику сервера permission.manageWebhooks=Управлять вебхуками (webhooks) permission.mentionEveryone=Упоминание `@everyone`, `@here` и всех ролей permission.moveMembers=Перемещать участников (голосовой канал) @@ -117,6 +172,7 @@ permission.prioritySpeaker=Приоритетный режим (голосово permission.readMessageHistory=Читать историю сообщений permission.requestToSpeak=Попросить выступить permission.sendMessages=Отправлять сообщения +permission.sendMessagesInThreads=Отправлять сообщения в ветках permission.sendTTSMessages=Отправка сообщения text-to-speech permission.speak=Говорить (голосовой канал) permission.stream=Видео @@ -126,6 +182,7 @@ permission.useVAD=Использовать режим активации по г permission.viewAuditLog=Просматривать журнал аудита permission.viewChannel=Просматривать каналы permission.viewGuildInsights=Просматривать аналитику сервера + utils.message.useThisChannel=Пожалуйста, используйте {0} для этой команды. utils.message.commandNotAvailableInDm=Эта команда недоступна в личных сообщениях. utils.colors.black=чёрный,чёрн,чё,черный,черн,че diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties b/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties index 5218c712a0..dd13431931 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties @@ -5,6 +5,57 @@ argumentParser.error.notAllValid=给参数`{0}`提供了{1}个值,但其中{2, argumentParser.error.unknownConverterType=指定了未知的转换器类型:`{0}` argumentParser.error.noFilledArguments=此指令需要{0}个参数。 argumentParser.error.someFilledArguments=此命令需要{0}个参数,但只填入了{1}个参数。 + +channelType.dm=私信 +channelType.groupDm=群组私信 +channelType.guildCategory=类别 +channelType.guildNews=新闻 +channelType.guildStageVoice=讲堂 +channelType.guildStore=Store +channelType.guildText=文字 +channelType.guildVoice=语音 +channelType.publicNewsThread=News Thread +channelType.publicGuildThread=Public Thread +channelType.privateThread=Private Thread +channelType.unknown=未知 + +checks.responseTemplate=**错误**:{0} + +checks.inChannel.failed=必须在**{0}**中 +checks.notInChannel.failed=必须不在**{0}**中 +checks.inCategory.failed=必须在类别**{0}**中 +checks.notInCategory.failed=必须不在类别**{0}**中 +checks.channelHigher.failed=必须在一个高于**{0}**的频道中 +checks.channelLower.failed=必须不在一个低于**{0}**的频道中 +checks.channelHigherOrEqual.failed=必须在**{0}**或更高的频道 +checks.channelLowerOrEqual.failed=必须在**{0}**或更低的频道 + +checks.anyGuild.failed=必须在服务器中 +checks.noGuild.failed=必须不在服务器中 +checks.inGuild.failed=必须在**{0}**服务器中 +checks.notInGuild.failed=必须不在**{0}**服务器中 + +checks.channelType.failed=必须在**{0}**型频道内 +checks.notChannelType.failed=必须不在**{0}**型频道内 + +checks.hasPermission.failed=必须有权限:**{0}** +checks.notHasPermission.failed=必须没有权限:**{0}** + +checks.isBot.failed=必须是机器人 +checks.isNotBot.failed=必须不是机器人 + +checks.isInThread.failed=必须在分区内 +checks.isNotInThread.failed=必须不在分区内 + +checks.hasRole.failed=必须属于身份组**{0}** +checks.notHasRole.failed=必须不属于身份组**{0}** +checks.topRoleEqual.failed=最高身份组必须为**{0}** +checks.topRoleNotEqual.failed=最高身份组必须不为**{0}** +checks.topRoleHigher.failed=最高身份组必须高于**{0}** +checks.topRoleLower.failed=最高身份组必须低于**{0}** +checks.topRoleHigherOrEqual.failed=最高身份组必须为**{0}**或更高 +checks.topRoleLowerOrEqual.failed=最高身份组必须为**{0}**或更低 + commands.defaultDescription=未提供说明。 commands.error.missingBotPermissions=我没有运行该指令的权限!\n\n**缺少权限:**{0} commands.error.user=不好意思,在处理指令时**发生了错误**。请告知服务器的管理人员。 @@ -92,6 +143,7 @@ paginator.button.group.switch=下一组 paginator.button.less=更少 paginator.footer.page=第{0}页,共{1}页 paginator.footer.group=第{0}组,共{1}组 + permission.addReactions=添加反应 permission.administrator=管理员 permission.all=所有权限 @@ -100,6 +152,8 @@ permission.banMembers=封禁成员 permission.changeNickname=修改昵称 permission.connect=连接至语音 permission.createInstantInvite=创建邀请 +permission.createPrivateThreads=Create Private Threads +permission.createPublicThreads=Create Public Threads permission.deafenMembers=屏蔽成员语音接收 permission.embedLinks=嵌入链接 permission.kickMembers=踢除成员 @@ -109,6 +163,7 @@ permission.manageGuild=管理服务器 permission.manageMessages=管理消息 permission.manageNicknames=管理昵称 permission.manageRoles=管理身份组 +permission.manageThreads=查看服务器分析 permission.manageWebhooks=管理 webhooks permission.mentionEveryone=@全体成员 permission.moveMembers=移动成员 @@ -117,6 +172,7 @@ permission.prioritySpeaker=主要发言者 permission.readMessageHistory=阅读消息历史记录 permission.requestToSpeak=请求发言 permission.sendMessages=发送消息 +permission.sendMessagesInThreads=Send Messages In Threads permission.sendTTSMessages=发送文字转语音消息 permission.speak=语音讲话 permission.stream=视频 @@ -126,6 +182,7 @@ permission.useVAD=使用语音活动 permission.viewAuditLog=查看审计日志 permission.viewChannel=查看频道 permission.viewGuildInsights=查看服务器分析 + utils.message.useThisChannel=请在{0}使用此指令。 utils.message.commandNotAvailableInDm=该指令在私信中不可用。 utils.colors.black=黑,黑色 From 57ae131776bdf8700d02bd70baf4f9c217fdcd64 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 12 Sep 2021 15:03:54 +0100 Subject: [PATCH 054/131] Add Weblate to README --- README.md | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 97bb578388..6b28a199ef 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # Kord Extensions -[![Docs: Click here](https://img.shields.io/static/v1?label=Docs&message=Click%20here&color=7289DA&style=for-the-badge&logo=read-the-docs)](https://kordex.kotlindiscord.com/) [![Discord: Click here](https://img.shields.io/static/v1?label=Discord&message=Click%20here&color=7289DA&style=for-the-badge&logo=discord)](https://discord.gg/gjXqqCS) [![Build Status](https://img.shields.io/github/workflow/status/Kotlin-Discord/kord-extensions/CI/root?logo=github&style=for-the-badge)](https://github.com/Kotlin-Discord/kord-extensions/actions?query=workflow%3ACI+branch%3Aroot)
+[![Docs: Click here](https://img.shields.io/static/v1?label=Docs&message=Click%20here&color=7289DA&style=for-the-badge&logo=read-the-docs)](https://kordex.kotlindiscord.com/) [![Discord: Click here](https://img.shields.io/static/v1?label=Discord&message=Click%20here&color=7289DA&style=for-the-badge&logo=discord)](https://discord.gg/gjXqqCS)
+[![Build Status](https://img.shields.io/github/workflow/status/Kotlin-Discord/kord-extensions/CI/root?logo=github&style=for-the-badge)](https://github.com/Kotlin-Discord/kord-extensions/actions?query=workflow%3ACI+branch%3Aroot) [![Weblate project translated](https://img.shields.io/weblate/progress/kord-extensions?style=for-the-badge)]((https://hosted.weblate.org/engage/kord-extensions/))
[![Release](https://img.shields.io/nexus/r/com.kotlindiscord.kord.extensions/kord-extensions?nexusVersion=3&logo=gradle&color=blue&label=Release&server=https%3A%2F%2Fmaven.kotlindiscord.com&style=for-the-badge)](https://maven.kotlindiscord.com/#browse/browse:maven-releases:com%2Fkotlindiscord%2Fkord%2Fextensions%2Fkord-extensions) [![Snapshot](https://img.shields.io/nexus/s/com.kotlindiscord.kord.extensions/kord-extensions?logo=gradle&color=orange&label=Snapshot&server=https%3A%2F%2Fmaven.kotlindiscord.com&style=for-the-badge)](https://maven.kotlindiscord.com/#browse/browse:maven-snapshots:com%2Fkotlindiscord%2Fkord%2Fextensions%2Fkord-extensions) +[![Translation status](https://hosted.weblate.org/widgets/kord-extensions/-/main/287x66-grey.png)](https://hosted.weblate.org/engage/kord-extensions/) + Kord Extensions is an addon for the excellent [Kord library](https://github.com/kordlib/kord). It intends to provide a framework for larger bot projects, with easy-to-use commands, rich argument parsing and event handling, wrapped up into individual extension classes. @@ -14,22 +17,3 @@ for our fairly object-oriented design, especially where it comes to its extensio Discord.py). Despite this, we still strive to provide an idiomatic API that makes full use of Kotlin's niceties. If you're ready to get started, please [take a look at the documentation](https://kordex.kotlindiscord.com/). - -# Why not kordx.commands? - -Kord has released their own command framework, [kordx.commands](https://github.com/kordlib/kordx.commands). It's -a competent library, but it takes some very different approaches to solving the same problems Kord Extensions does. -Most notably, it requires the use of [kapt](https://kotlinlang.org/docs/reference/kapt.html) and makes use of an -annotation-based autowire system for getting things registered. - -In contrast, Kord Extensions provides a less magical approach that is more closely tied to object-oriented -programming, and may be more suitable for embedding into other applications. In addition, it provides many useful -utilities and niceties that make working with Kord a breeze. At the end of the day, though, the -choice is yours - both approaches have pros and cons, and it's worth checking both out to see what you like -better! - -# Under Development - -This file is in an early state, and we're working on bringing over our framework from our bot project. Once we're -happy with what we've done, and we've written up some documentation, we'll update this file and make a proper -release. From 8217e158bf43d7f292d63a2130566979634ed5b3 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 13 Sep 2021 13:24:12 +0100 Subject: [PATCH 055/131] [#77] Make more ACR methods `open` --- .../application/ApplicationCommandRegistry.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 e0baeb3865..f2b7c0dd91 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 @@ -38,25 +38,25 @@ public open class ApplicationCommandRegistry : KoinComponent { public open val bot: ExtensibleBot by inject() /** Kord instance, backing the ExtensibleBot. **/ - public val kord: Kord by inject() + public open val kord: Kord by inject() /** Command parser to use for slash commands. **/ public open val argumentParser: SlashCommandParser = SlashCommandParser() /** Translations provider, for retrieving translations. **/ - public val translationsProvider: TranslationsProvider by inject() + public open val translationsProvider: TranslationsProvider by inject() /** Mapping of Discord-side command ID to a message command object. **/ - public val messageCommands: MutableMap> = mutableMapOf() + public open val messageCommands: MutableMap> = mutableMapOf() /** Mapping of Discord-side command ID to a slash command object. **/ - public val slashCommands: MutableMap> = mutableMapOf() + public open val slashCommands: MutableMap> = mutableMapOf() /** Mapping of Discord-side command ID to a user command object. **/ - public val userCommands: MutableMap> = mutableMapOf() + public open val userCommands: MutableMap> = mutableMapOf() /** Whether the initial sync has been finished, and commands should be registered directly. **/ - public var initialised: Boolean = false + public open var initialised: Boolean = false /** Quick access to the human-readable name for a Discord application command type. **/ public val ApplicationCommandType.name: String @@ -69,7 +69,7 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Handles the initial registration of commands, after extensions have been loaded. **/ - public suspend fun initialRegistration() { + public open suspend fun initialRegistration() { if (!bot.settings.applicationCommandsBuilder.register) { logger.debug { "Application command registration is disabled, pairing existing commands with extension commands" @@ -556,7 +556,7 @@ public open class ApplicationCommandRegistry : KoinComponent { // region: Event handlers /** Event handler for message commands. **/ - public suspend fun handle(event: MessageCommandInteractionCreateEvent) { + public open suspend fun handle(event: MessageCommandInteractionCreateEvent) { val commandId = event.interaction.invokedCommandId val command = messageCommands[commandId] @@ -566,7 +566,7 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Event handler for slash commands. **/ - public suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { + public open suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { val commandId = event.interaction.command.rootId val command = slashCommands[commandId] @@ -576,7 +576,7 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Event handler for user commands. **/ - public suspend fun handle(event: UserCommandInteractionCreateEvent) { + public open suspend fun handle(event: UserCommandInteractionCreateEvent) { val commandId = event.interaction.invokedCommandId val command = userCommands[commandId] @@ -590,7 +590,7 @@ public open class ApplicationCommandRegistry : KoinComponent { // region: Extensions /** Registration logic for slash commands, extracted for clarity. **/ - public suspend fun ChatInputCreateBuilder.register(locale: Locale, command: SlashCommand<*, *>) { + public open suspend fun ChatInputCreateBuilder.register(locale: Locale, command: SlashCommand<*, *>) { this.defaultPermission = command.guildId == null || command.allowByDefault if (command.hasBody) { @@ -667,18 +667,18 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Registration logic for message commands, extracted for clarity. **/ @Suppress("UnusedPrivateMember") // Only for now... - public fun MessageCommandCreateBuilder.register(locale: Locale, command: MessageCommand<*>) { + public open fun MessageCommandCreateBuilder.register(locale: Locale, command: MessageCommand<*>) { this.defaultPermission = command.guildId == null || command.allowByDefault } /** Registration logic for user commands, extracted for clarity. **/ @Suppress("UnusedPrivateMember") // Only for now... - public fun UserCommandCreateBuilder.register(locale: Locale, command: UserCommand<*>) { + public open fun UserCommandCreateBuilder.register(locale: Locale, command: UserCommand<*>) { this.defaultPermission = command.guildId == null || command.allowByDefault } /** Check whether the type and name of an extension-registered application command matches a Discord one. **/ - public fun ApplicationCommand<*>.matches( + public open fun ApplicationCommand<*>.matches( locale: Locale, other: dev.kord.core.entity.application.ApplicationCommand ): Boolean = getTranslatedName(locale) == other.name && type == other.type From c95914fd6c40cd520187ac8754474dd349271fea Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 13 Sep 2021 18:49:09 +0100 Subject: [PATCH 056/131] Small fixes to paginator/translations --- .../kord/extensions/pagination/BaseButtonPaginator.kt | 4 ++-- .../src/main/resources/translations/kordex/strings.properties | 2 +- .../resources/translations/kordex/strings_en_GB.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index b28716e373..268bd33139 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -44,7 +44,7 @@ public abstract class BaseButtonPaginator( null } - private val lastRowNumber = components.rows.size - 1 + private val lastRowNumber by lazy { components.rows.size - 1 } private val secondRowNumber = 1 /** Button builder representing the button that switches to the first page. **/ @@ -67,7 +67,7 @@ public abstract class BaseButtonPaginator( /** Whether it's possible for us to have a row of group-switching buttons. **/ @Suppress("MagicNumber") - public val canUseSwitchingButtons: Boolean = allGroups.size in 3..5 && "" !in allGroups + public val canUseSwitchingButtons: Boolean by lazy { allGroups.size in 3..5 && "" !in allGroups } /** A button-oriented check function that matches based on the [owner] property. **/ public val defaultCheck: Check = { diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 79988157d8..848f4a30d6 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -9,7 +9,7 @@ argumentParser.error.someFilledArguments=This command has {0} required {0, plura channelType.dm=DM channelType.groupDm=Group DM channelType.guildCategory=Category -channelType.guildNews=News +channelType.guildNews=Announcement channelType.guildStageVoice=Stage channelType.guildStore=Store channelType.guildText=Text diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index 79988157d8..848f4a30d6 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -9,7 +9,7 @@ argumentParser.error.someFilledArguments=This command has {0} required {0, plura channelType.dm=DM channelType.groupDm=Group DM channelType.guildCategory=Category -channelType.guildNews=News +channelType.guildNews=Announcement channelType.guildStageVoice=Stage channelType.guildStore=Store channelType.guildText=Text From 5cfcb579822f8fbf4cedff98fbea0b017145cfa0 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 14 Sep 2021 18:32:42 +0100 Subject: [PATCH 057/131] Threading changes for the mappings extension --- .../extra/mappings/MappingsExtension.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index b07c3c887f..e2d40c4f7f 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -23,6 +23,8 @@ import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.core.behavior.channel.withTyping import dev.kord.core.event.message.MessageCreateEvent +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.withContext import me.shedaniel.linkie.* import me.shedaniel.linkie.namespaces.* import me.shedaniel.linkie.utils.MappingsQuery @@ -1045,7 +1047,7 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) { + ) = withContext(newSingleThreadContext("c: $givenQuery")) { val provider = if (version == null) { if (channel != null) { namespace.getProvider( @@ -1078,7 +1080,7 @@ class MappingsExtension : Extension() { ) } catch (e: NullPointerException) { message.respond(e.localizedMessage) - return@queryClasses + return@withContext } pages = classesToPages(namespace, result) @@ -1086,7 +1088,7 @@ class MappingsExtension : Extension() { if (pages.isEmpty()) { message.respond("No results found") - return + return@withContext } val meta = provider.get() @@ -1153,7 +1155,7 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) { + ) = withContext(newSingleThreadContext("f: $givenQuery")) { val provider = if (version == null) { if (channel != null) { namespace.getProvider( @@ -1186,7 +1188,7 @@ class MappingsExtension : Extension() { ) } catch (e: NullPointerException) { message.respond(e.localizedMessage) - return@queryFields + return@withContext } pages = fieldsToPages(namespace, provider.get(), result) @@ -1194,7 +1196,7 @@ class MappingsExtension : Extension() { if (pages.isEmpty()) { message.respond("No results found") - return + return@withContext } val meta = provider.get() @@ -1261,7 +1263,7 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) { + ) = withContext(newSingleThreadContext("m: $givenQuery")) { val provider = if (version == null) { if (channel != null) { namespace.getProvider( @@ -1294,7 +1296,7 @@ class MappingsExtension : Extension() { ) } catch (e: NullPointerException) { message.respond(e.localizedMessage) - return@queryMethods + return@withContext } pages = methodsToPages(namespace, provider.get(), result) @@ -1302,7 +1304,7 @@ class MappingsExtension : Extension() { if (pages.isEmpty()) { message.respond("No results found") - return + return@withContext } val meta = provider.get() From a7632376034ec3c2fd95c3dfd7cc5e48d031331f Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 14 Sep 2021 18:50:28 +0100 Subject: [PATCH 058/131] Mappings: Close extra threads --- .../extra/mappings/MappingsExtension.kt | 516 +++++++++--------- 1 file changed, 270 insertions(+), 246 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index e2d40c4f7f..b2b2e11355 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -1047,107 +1047,115 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) = withContext(newSingleThreadContext("c: $givenQuery")) { - val provider = if (version == null) { - if (channel != null) { - namespace.getProvider( - namespace.getDefaultVersion { channel } - ) - } else { - MappingsProvider.empty(namespace) - } - } else { - namespace.getProvider(version.version) - } + ) { + val context = newSingleThreadContext("c: $givenQuery") + + try { + withContext(context) { + val provider = if (version == null) { + if (channel != null) { + namespace.getProvider( + namespace.getDefaultVersion { channel } + ) + } else { + MappingsProvider.empty(namespace) + } + } else { + namespace.getProvider(version.version) + } - provider.injectDefaultVersion( - namespace.getDefaultProvider { - channel ?: namespace.getDefaultMappingChannel() - } - ) - - val query = givenQuery.replace(".", "/") - var pages: List> - - message.channel.withTyping { - @Suppress("TooGenericExceptionCaught") - val result = try { - MappingsQuery.queryClasses( - QueryContext( - provider = provider, - searchKey = query - ) + provider.injectDefaultVersion( + namespace.getDefaultProvider { + channel ?: namespace.getDefaultMappingChannel() + } ) - } catch (e: NullPointerException) { - message.respond(e.localizedMessage) - return@withContext - } - pages = classesToPages(namespace, result) - } + val query = givenQuery.replace(".", "/") + var pages: List> + + message.channel.withTyping { + @Suppress("TooGenericExceptionCaught") + val result = try { + MappingsQuery.queryClasses( + QueryContext( + provider = provider, + searchKey = query + ) + ) + } catch (e: NullPointerException) { + message.respond(e.localizedMessage) + return@withContext + } - if (pages.isEmpty()) { - message.respond("No results found") - return@withContext - } + pages = classesToPages(namespace, result) + } - val meta = provider.get() + if (pages.isEmpty()) { + message.respond("No results found") + return@withContext + } - val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") - val pageTitle = "List of ${meta.name} classes: ${meta.version}" + val meta = provider.get() - val shortPages = mutableListOf() - val longPages = mutableListOf() + val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") + val pageTitle = "List of ${meta.name} classes: ${meta.version}" - pages.forEach { (short, long) -> - shortPages.add(short) - longPages.add(long) - } + val shortPages = mutableListOf() + val longPages = mutableListOf() - shortPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for more", + pages.forEach { (short, long) -> + shortPages.add(short) + longPages.add(long) + } - Page { - description = it - title = pageTitle + shortPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for more", - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + Page { + description = it + title = pageTitle + + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } - ) - } - if (shortPages != longPages) { - longPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for less", + if (shortPages != longPages) { + longPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for less", - Page { - description = it - title = pageTitle + Page { + description = it + title = pageTitle - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } + } + + val paginator = MessageButtonPaginator( + targetMessage = event.message, + pages = pagesObj, + keepEmbed = true, + owner = message.author, + timeoutSeconds = getTimeout(), + locale = getLocale(), ) + + paginator.send() } + } finally { + context.close() } - - val paginator = MessageButtonPaginator( - targetMessage = event.message, - pages = pagesObj, - keepEmbed = true, - owner = message.author, - timeoutSeconds = getTimeout(), - locale = getLocale(), - ) - - paginator.send() } private suspend fun ChatCommandContext.queryFields( @@ -1155,107 +1163,115 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) = withContext(newSingleThreadContext("f: $givenQuery")) { - val provider = if (version == null) { - if (channel != null) { - namespace.getProvider( - namespace.getDefaultVersion { channel } - ) - } else { - MappingsProvider.empty(namespace) - } - } else { - namespace.getProvider(version.version) - } + ) { + val context = newSingleThreadContext("f: $givenQuery") + + try { + withContext(context) { + val provider = if (version == null) { + if (channel != null) { + namespace.getProvider( + namespace.getDefaultVersion { channel } + ) + } else { + MappingsProvider.empty(namespace) + } + } else { + namespace.getProvider(version.version) + } - provider.injectDefaultVersion( - namespace.getDefaultProvider { - channel ?: namespace.getDefaultMappingChannel() - } - ) - - val query = givenQuery.replace(".", "/") - var pages: List> - - message.channel.withTyping { - @Suppress("TooGenericExceptionCaught") - val result = try { - MappingsQuery.queryFields( - QueryContext( - provider = provider, - searchKey = query - ) + provider.injectDefaultVersion( + namespace.getDefaultProvider { + channel ?: namespace.getDefaultMappingChannel() + } ) - } catch (e: NullPointerException) { - message.respond(e.localizedMessage) - return@withContext - } - pages = fieldsToPages(namespace, provider.get(), result) - } + val query = givenQuery.replace(".", "/") + var pages: List> + + message.channel.withTyping { + @Suppress("TooGenericExceptionCaught") + val result = try { + MappingsQuery.queryFields( + QueryContext( + provider = provider, + searchKey = query + ) + ) + } catch (e: NullPointerException) { + message.respond(e.localizedMessage) + return@withContext + } - if (pages.isEmpty()) { - message.respond("No results found") - return@withContext - } + pages = fieldsToPages(namespace, provider.get(), result) + } - val meta = provider.get() + if (pages.isEmpty()) { + message.respond("No results found") + return@withContext + } - val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") - val pageTitle = "List of ${meta.name} fields: ${meta.version}" + val meta = provider.get() - val shortPages = mutableListOf() - val longPages = mutableListOf() + val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") + val pageTitle = "List of ${meta.name} fields: ${meta.version}" - pages.forEach { (short, long) -> - shortPages.add(short) - longPages.add(long) - } + val shortPages = mutableListOf() + val longPages = mutableListOf() - shortPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for more", + pages.forEach { (short, long) -> + shortPages.add(short) + longPages.add(long) + } - Page { - description = it - title = pageTitle + shortPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for more", - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + Page { + description = it + title = pageTitle + + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } - ) - } - if (shortPages != longPages) { - longPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for less", + if (shortPages != longPages) { + longPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for less", - Page { - description = it - title = pageTitle + Page { + description = it + title = pageTitle - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } + } + + val paginator = MessageButtonPaginator( + targetMessage = event.message, + pages = pagesObj, + keepEmbed = true, + owner = message.author, + timeoutSeconds = getTimeout(), + locale = getLocale(), ) + + paginator.send() } + } finally { + context.close() } - - val paginator = MessageButtonPaginator( - targetMessage = event.message, - pages = pagesObj, - keepEmbed = true, - owner = message.author, - timeoutSeconds = getTimeout(), - locale = getLocale(), - ) - - paginator.send() } private suspend fun ChatCommandContext.queryMethods( @@ -1263,107 +1279,115 @@ class MappingsExtension : Extension() { givenQuery: String, version: MappingsContainer?, channel: String? = null - ) = withContext(newSingleThreadContext("m: $givenQuery")) { - val provider = if (version == null) { - if (channel != null) { - namespace.getProvider( - namespace.getDefaultVersion { channel } - ) - } else { - MappingsProvider.empty(namespace) - } - } else { - namespace.getProvider(version.version) - } + ) { + val context = newSingleThreadContext("m: $givenQuery") + + try { + withContext(context) { + val provider = if (version == null) { + if (channel != null) { + namespace.getProvider( + namespace.getDefaultVersion { channel } + ) + } else { + MappingsProvider.empty(namespace) + } + } else { + namespace.getProvider(version.version) + } - provider.injectDefaultVersion( - namespace.getDefaultProvider { - channel ?: namespace.getDefaultMappingChannel() - } - ) - - val query = givenQuery.replace(".", "/") - var pages: List> - - message.channel.withTyping { - @Suppress("TooGenericExceptionCaught") - val result = try { - MappingsQuery.queryMethods( - QueryContext( - provider = provider, - searchKey = query - ) + provider.injectDefaultVersion( + namespace.getDefaultProvider { + channel ?: namespace.getDefaultMappingChannel() + } ) - } catch (e: NullPointerException) { - message.respond(e.localizedMessage) - return@withContext - } - pages = methodsToPages(namespace, provider.get(), result) - } + val query = givenQuery.replace(".", "/") + var pages: List> + + message.channel.withTyping { + @Suppress("TooGenericExceptionCaught") + val result = try { + MappingsQuery.queryMethods( + QueryContext( + provider = provider, + searchKey = query + ) + ) + } catch (e: NullPointerException) { + message.respond(e.localizedMessage) + return@withContext + } - if (pages.isEmpty()) { - message.respond("No results found") - return@withContext - } + pages = methodsToPages(namespace, provider.get(), result) + } - val meta = provider.get() + if (pages.isEmpty()) { + message.respond("No results found") + return@withContext + } - val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") - val pageTitle = "List of ${meta.name} methods: ${meta.version}" + val meta = provider.get() - val shortPages = mutableListOf() - val longPages = mutableListOf() + val pagesObj = Pages("${EXPAND_EMOJI.mention} for more") + val pageTitle = "List of ${meta.name} methods: ${meta.version}" - pages.forEach { (short, long) -> - shortPages.add(short) - longPages.add(long) - } + val shortPages = mutableListOf() + val longPages = mutableListOf() - shortPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for more", + pages.forEach { (short, long) -> + shortPages.add(short) + longPages.add(long) + } - Page { - description = it - title = pageTitle + shortPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for more", - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + Page { + description = it + title = pageTitle + + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } - ) - } - if (shortPages != longPages) { - longPages.forEach { - pagesObj.addPage( - "${EXPAND_EMOJI.mention} for less", + if (shortPages != longPages) { + longPages.forEach { + pagesObj.addPage( + "${EXPAND_EMOJI.mention} for less", - Page { - description = it - title = pageTitle + Page { + description = it + title = pageTitle - footer { - text = PAGE_FOOTER - icon = PAGE_FOOTER_ICON - } + footer { + text = PAGE_FOOTER + icon = PAGE_FOOTER_ICON + } + } + ) } + } + + val paginator = MessageButtonPaginator( + targetMessage = event.message, + pages = pagesObj, + keepEmbed = true, + owner = message.author, + timeoutSeconds = getTimeout(), + locale = getLocale(), ) + + paginator.send() } + } finally { + context.close() } - - val paginator = MessageButtonPaginator( - targetMessage = event.message, - pages = pagesObj, - keepEmbed = true, - owner = message.author, - timeoutSeconds = getTimeout(), - locale = getLocale(), - ) - - paginator.send() } private suspend fun getTimeout() = builder.config.getTimeout() From 8da690c68c0e5f03cc473aa5172b6e513f3451e7 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 14 Sep 2021 18:56:33 +0100 Subject: [PATCH 059/131] Silly translation typo --- .../src/main/resources/translations/kordex/strings.properties | 2 +- .../main/resources/translations/kordex/strings_en_GB.properties | 2 +- .../main/resources/translations/kordex/strings_fr_FR.properties | 2 +- .../main/resources/translations/kordex/strings_pt_PT.properties | 2 +- .../main/resources/translations/kordex/strings_ru_RU.properties | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index 848f4a30d6..d714898225 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -26,7 +26,7 @@ checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category: **{0}** checks.notInCategory.failed=Must not be in category: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index 848f4a30d6..d714898225 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -26,7 +26,7 @@ checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category: **{0}** checks.notInCategory.failed=Must not be in category: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties index 47bae4d697..a569b6e807 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties @@ -26,7 +26,7 @@ checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category\: **{0}** checks.notInCategory.failed=Must not be in category\: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties index c232eb78c2..9f84147fff 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_pt_PT.properties @@ -26,7 +26,7 @@ checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category\: **{0}** checks.notInCategory.failed=Must not be in category\: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties index eca24e8768..77d6a15a6b 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties @@ -26,7 +26,7 @@ checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Разрешено только в категории\: **{0}** checks.notInCategory.failed=Не разрешено в категории\: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** +checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel From dd2821754f2fa18ea1f111a26ecc89e90c80fd7e Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 14 Sep 2021 22:15:21 +0100 Subject: [PATCH 060/131] Bringing things into line with the new docs --- .../kotlindiscord/kord/extensions/checks/CheckUtils.kt | 8 +++----- .../kotlindiscord/kord/extensions/extensions/Extension.kt | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 90e185733c..37ede67340 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -2,7 +2,7 @@ package com.kotlindiscord.kord.extensions.checks -import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.* @@ -422,9 +422,7 @@ public suspend fun userFor(event: Event): UserBehavior? { } } -/** Wrap an existing check, calling it but ensuring that no message is produced. **/ -public suspend fun Check<*>.silenced(): Check<*> = { - this@silenced() - +/** Silence the current check by removing any message it may have set. **/ +public fun CheckContext<*>.silence() { message = 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 c36ba12635..4742920f95 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 @@ -40,10 +40,10 @@ public abstract class Extension : KoinComponent { public open val kord: Kord by inject() /** Message command registry. **/ - internal val chatCommandRegistry: ChatCommandRegistry by inject() + public open val chatCommandRegistry: ChatCommandRegistry by inject() /** Slash command registry. **/ - public val applicationCommandRegistry: ApplicationCommandRegistry by inject() + public open val applicationCommandRegistry: ApplicationCommandRegistry by inject() /** * The name of this extension. From a8e2d719f44cd6563053ca2119e9027dab76039a Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 15 Sep 2021 11:12:59 +0100 Subject: [PATCH 061/131] Add bot builder DSL annotations to hooks builders --- .idea/runConfigurations.xml | 10 ++++++++++ .../kord/extensions/builders/ExtensibleBotBuilder.kt | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 .idea/runConfigurations.xml diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000000..797acea53e --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index cd9102e0a0..42b7bef996 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -546,6 +546,7 @@ public open class ExtensibleBotBuilder { * Register a lambda to be called after all the extensions in the [ExtensionsBuilder] have been added. This * will be called regardless of how many were successfully set up. */ + @BotBuilderDSL public fun afterExtensionsAdded(body: suspend ExtensibleBot.() -> Unit): Boolean = afterExtensionsAddedList.add(body) @@ -553,6 +554,7 @@ public open class ExtensibleBotBuilder { * Register a lambda to be called after Koin has been set up. You can use this to register overriding modules * via `loadModule` before the modules are actually accessed. */ + @BotBuilderDSL public fun afterKoinSetup(body: () -> Unit): Boolean = afterKoinSetupList.add(body) @@ -560,18 +562,21 @@ public open class ExtensibleBotBuilder { * Register a lambda to be called before Koin has been set up. You can use this to register Koin modules * early, if needed. */ + @BotBuilderDSL public fun beforeKoinSetup(body: () -> Unit): Boolean = beforeKoinSetupList.add(body) /** * Register a lambda to be called before all the extensions in the [ExtensionsBuilder] have been added. */ + @BotBuilderDSL public fun beforeExtensionsAdded(body: suspend ExtensibleBot.() -> Unit): Boolean = beforeExtensionsAddedList.add(body) /** * Register a lambda to be called just before the bot tries to connect to Discord. */ + @BotBuilderDSL public fun beforeStart(body: suspend ExtensibleBot.() -> Unit): Boolean = beforeStartList.add(body) @@ -579,18 +584,21 @@ public open class ExtensibleBotBuilder { * Register a lambda to be called right after the [ExtensibleBot] object has been created, before it gets set * up. */ + @BotBuilderDSL public fun created(body: suspend ExtensibleBot.() -> Unit): Boolean = createdList.add(body) /** * Register a lambda to be called after any extension is successfully added to the bot. */ + @BotBuilderDSL public fun extensionAdded(body: suspend ExtensibleBot.(extension: Extension) -> Unit): Boolean = extensionAddedList.add(body) /** * Register a lambda to be called after the [ExtensibleBot] object has been created and set up. */ + @BotBuilderDSL public fun setup(body: suspend ExtensibleBot.() -> Unit): Boolean = setupList.add(body) From f403569e2073eeb7751ba3ea54a59df6ebab2cd3 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 15 Sep 2021 15:03:57 +0100 Subject: [PATCH 062/131] Fix some regressions --- .../extensions/builders/ExtensibleBotBuilder.kt | 13 +++++++++++-- .../application/slash/PublicSlashCommand.kt | 2 +- .../commands/application/slash/_Functions.kt | 8 ++++++++ .../kotlindiscord/kord/extensions/test/bot/Bot.kt | 8 ++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 42b7bef996..7acd069a67 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -177,11 +177,20 @@ public open class ExtensibleBotBuilder { /** * DSL function used to configure the bot's intents. * + * @param addDefaultIntents Whether to automatically add all non-privileged intents to the builder before running + * the given lambda. + * * @see Intents.IntentsBuilder */ @BotBuilderDSL - public fun intents(builder: Intents.IntentsBuilder.() -> Unit) { - this.intentsBuilder = builder + public fun intents(addDefaultIntents: Boolean = true, builder: Intents.IntentsBuilder.() -> Unit) { + this.intentsBuilder = { + if (addDefaultIntents) { + +Intents.nonPrivileged + } + + builder() + } } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 2ea16f44ec..e54311f4a9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -25,7 +25,7 @@ public class PublicSlashCommand
( public override val parentCommand: SlashCommand<*, *>? = null, public override val parentGroup: SlashGroup? = null ) : SlashCommand, A>(extension) { - /** @suppress Internal guilder **/ + /** @suppress Internal builder **/ public var initialResponseBuilder: InitialPublicSlashResponseBehavior = null /** Call this to open with a response, omit it to ack instead. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt index 83d4ccee38..266c0d8135 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/_Functions.kt @@ -78,6 +78,8 @@ public suspend fun SlashCommand<*, *>.ephemeralSubCommand( public fun SlashCommand<*, *>.ephemeralSubCommand( commandObj: EphemeralSlashCommand ): EphemeralSlashCommand { + commandObj.guildId = null + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { throw InvalidCommandException( commandObj.name, @@ -145,6 +147,8 @@ public suspend fun SlashCommand<*, *>.publicSubCommand( public fun SlashCommand<*, *>.publicSubCommand( commandObj: PublicSlashCommand ): PublicSlashCommand { + commandObj.guildId = null + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { throw InvalidCommandException( commandObj.name, @@ -212,6 +216,8 @@ public suspend fun SlashGroup.ephemeralSubCommand( public fun SlashGroup.ephemeralSubCommand( commandObj: EphemeralSlashCommand ): EphemeralSlashCommand { + commandObj.guildId = null + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { throw InvalidCommandException( commandObj.name, @@ -279,6 +285,8 @@ public suspend fun SlashGroup.publicSubCommand( public fun SlashGroup.publicSubCommand( commandObj: PublicSlashCommand ): PublicSlashCommand { + commandObj.guildId = null + if (subCommands.size >= SUBCOMMAND_AND_GROUP_LIMIT) { throw InvalidCommandException( commandObj.name, diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 1edd13f7e2..e71f8122ab 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -28,6 +28,14 @@ suspend fun main() { defaultGuild("787452339908116521") } + intents { +// +Intent.GuildMessages + } + + members { + none() + } + extensions { add(::TestExtension) From 51a6dea4cf05f6551ad7d0ffe37dbb5a00151794 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Sep 2021 12:12:48 +0200 Subject: [PATCH 063/131] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (192 of 192 strings) Co-authored-by: Hosted Weblate Co-authored-by: Leo Chen Translate-URL: https://hosted.weblate.org/projects/kord-extensions/main/zh_Hans/ Translation: Kord Extensions/Primary Translations --- .../kordex/strings_zh_CN.properties | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties b/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties index dd13431931..57285adebb 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_zh_CN.properties @@ -5,7 +5,6 @@ argumentParser.error.notAllValid=给参数`{0}`提供了{1}个值,但其中{2, argumentParser.error.unknownConverterType=指定了未知的转换器类型:`{0}` argumentParser.error.noFilledArguments=此指令需要{0}个参数。 argumentParser.error.someFilledArguments=此命令需要{0}个参数,但只填入了{1}个参数。 - channelType.dm=私信 channelType.groupDm=群组私信 channelType.guildCategory=类别 @@ -14,13 +13,11 @@ channelType.guildStageVoice=讲堂 channelType.guildStore=Store channelType.guildText=文字 channelType.guildVoice=语音 -channelType.publicNewsThread=News Thread -channelType.publicGuildThread=Public Thread -channelType.privateThread=Private Thread +channelType.publicNewsThread=新闻子区 +channelType.publicGuildThread=公共子区 +channelType.privateThread=私密子区 channelType.unknown=未知 - checks.responseTemplate=**错误**:{0} - checks.inChannel.failed=必须在**{0}**中 checks.notInChannel.failed=必须不在**{0}**中 checks.inCategory.failed=必须在类别**{0}**中 @@ -29,24 +26,18 @@ checks.channelHigher.failed=必须在一个高于**{0}**的频道中 checks.channelLower.failed=必须不在一个低于**{0}**的频道中 checks.channelHigherOrEqual.failed=必须在**{0}**或更高的频道 checks.channelLowerOrEqual.failed=必须在**{0}**或更低的频道 - checks.anyGuild.failed=必须在服务器中 checks.noGuild.failed=必须不在服务器中 checks.inGuild.failed=必须在**{0}**服务器中 checks.notInGuild.failed=必须不在**{0}**服务器中 - checks.channelType.failed=必须在**{0}**型频道内 checks.notChannelType.failed=必须不在**{0}**型频道内 - checks.hasPermission.failed=必须有权限:**{0}** checks.notHasPermission.failed=必须没有权限:**{0}** - checks.isBot.failed=必须是机器人 checks.isNotBot.failed=必须不是机器人 - checks.isInThread.failed=必须在分区内 checks.isNotInThread.failed=必须不在分区内 - checks.hasRole.failed=必须属于身份组**{0}** checks.notHasRole.failed=必须不属于身份组**{0}** checks.topRoleEqual.failed=最高身份组必须为**{0}** @@ -55,7 +46,6 @@ checks.topRoleHigher.failed=最高身份组必须高于**{0}** checks.topRoleLower.failed=最高身份组必须低于**{0}** checks.topRoleHigherOrEqual.failed=最高身份组必须为**{0}**或更高 checks.topRoleLowerOrEqual.failed=最高身份组必须为**{0}**或更低 - commands.defaultDescription=未提供说明。 commands.error.missingBotPermissions=我没有运行该指令的权限!\n\n**缺少权限:**{0} commands.error.user=不好意思,在处理指令时**发生了错误**。请告知服务器的管理人员。 @@ -143,7 +133,6 @@ paginator.button.group.switch=下一组 paginator.button.less=更少 paginator.footer.page=第{0}页,共{1}页 paginator.footer.group=第{0}组,共{1}组 - permission.addReactions=添加反应 permission.administrator=管理员 permission.all=所有权限 @@ -182,7 +171,6 @@ permission.useVAD=使用语音活动 permission.viewAuditLog=查看审计日志 permission.viewChannel=查看频道 permission.viewGuildInsights=查看服务器分析 - utils.message.useThisChannel=请在{0}使用此指令。 utils.message.commandNotAvailableInDm=该指令在私信中不可用。 utils.colors.black=黑,黑色 From addd2028816bef42aa3f0e935cb043402839d4c5 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Sep 2021 12:12:48 +0200 Subject: [PATCH 064/131] Translated using Weblate (Russian) Currently translated at 100.0% (192 of 192 strings) Co-authored-by: Gareth Coles Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/kord-extensions/main/ru/ Translation: Kord Extensions/Primary Translations --- .../translations/kordex/strings_ru_RU.properties | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties index 77d6a15a6b..54e8dd6278 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_ru_RU.properties @@ -5,7 +5,6 @@ argumentParser.error.notAllValid=Для аргумента `{0}` дано {1} {1 argumentParser.error.unknownConverterType=Указан неизвестный тип конвертера\: `{0}` argumentParser.error.noFilledArguments=У команды {0} {0, plural, one {обязательный аргумент} few {обязательных аргумента} other {обязательных аргументов}}. argumentParser.error.someFilledArguments=У команды {0} {0, plural, one {обязательный аргумент} few {обязательных аргумента} other {обязательных аргументов}}, но удалось заполнить только {1}. - channelType.dm=ЛС channelType.groupDm=Групповые ЛС channelType.guildCategory=Категория @@ -18,9 +17,7 @@ channelType.publicNewsThread=News Thread channelType.publicGuildThread=Public Thread channelType.privateThread=Private Thread channelType.unknown=Неизвестный - checks.responseTemplate=**Ошибка\:** {0} - checks.inChannel.failed=Must be in **{0}** checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Разрешено только в категории\: **{0}** @@ -29,24 +26,18 @@ checks.channelHigher.failed=Must be in a channel higher than **{0}** checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel - checks.anyGuild.failed=Разрешено только на сервере checks.noGuild.failed=Не разрешено на сервере checks.inGuild.failed=Разрешено только на сервере\: **{0}** checks.notInGuild.failed=Не разрешено на сервере\: **{0}** - checks.channelType.failed=Тип канала должен быть **{0}** checks.notChannelType.failed=Тип канала не должен быть **{0}** - checks.hasPermission.failed=Должно быть разрешение\: **{0}** checks.notHasPermission.failed=Не должно быть разрешения\: **{0}** - checks.isBot.failed=Разрешено только для бота checks.isNotBot.failed=Не разрешено для бота - checks.isInThread.failed=Разрешено только в ветке checks.isNotInThread.failed=Не разрешено в ветке - checks.hasRole.failed=Должна быть роль\: **{0}** checks.notHasRole.failed=Не дложно быть роли\: **{0}** checks.topRoleEqual.failed=Верхняя роль должна быть\: **{0}** @@ -55,7 +46,6 @@ checks.topRoleHigher.failed=Верхняя роль должна быть выш checks.topRoleLower.failed=Верхняя роль должна быть ниже, чем **{0}** checks.topRoleHigherOrEqual.failed=Верхняя роль должна быть **{0}** или выше checks.topRoleLowerOrEqual.failed=Верхняя роль должна быть **{0}** или ниже - commands.defaultDescription=Описание отсутствует. commands.error.missingBotPermissions=У меня не хватает разрешений для выполнения этой команды\!\n\n**Требуются разрешения\:** {0} commands.error.user=К сожалению, во время исполнения команды **произошла ошибка**. Сообщите об этом персоналу сервера. @@ -86,7 +76,7 @@ converters.guild.signatureType=сервер converters.guild.error.missing=Не могу найти сервер\: `{0}` converters.number.signatureType=число converters.number.error.invalid.defaultBase=Не могу распознать `{0}` как число. -converters.number.error.invalid.otherBase=Не могу распознать `{0}` как {1, plural, \=2 {двоичное число} \=8 {восьмеричное число} \=10 {десятичное число} \=16 {шестнадцатеричное число} other {число в системе счисления с основанием {1}}}. +converters.number.error.invalid.otherBase=Не могу распознать `{0}` как {1, plural, =2 {двоичное число} =8 {восьмеричное число} =10 {десятичное число} =16 {шестнадцатеричное число} other {число в системе счисления с основанием {1}}}. converters.member.signatureType=член converters.member.error.missing=Не могу найти члена\: {0} converters.member.error.invalid=Не могу распознать `{0}` как ID пользователя. @@ -143,7 +133,6 @@ paginator.button.group.switch=Следующая группа paginator.button.less=Меньше paginator.footer.page=Страница {0}/{1} paginator.footer.group=Группа {0}/{1} - permission.addReactions=Добавлять реакции permission.administrator=Администратор permission.all=Все разрешения @@ -182,7 +171,6 @@ permission.useVAD=Использовать режим активации по г permission.viewAuditLog=Просматривать журнал аудита permission.viewChannel=Просматривать каналы permission.viewGuildInsights=Просматривать аналитику сервера - utils.message.useThisChannel=Пожалуйста, используйте {0} для этой команды. utils.message.commandNotAvailableInDm=Эта команда недоступна в личных сообщениях. utils.colors.black=чёрный,чёрн,чё,черный,черн,че From 4f066512f23e3cef63d6784d24bb2e163c3bcc1a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Sep 2021 12:12:48 +0200 Subject: [PATCH 065/131] Translated using Weblate (French) Currently translated at 100.0% (192 of 192 strings) Translated using Weblate (French) Currently translated at 100.0% (192 of 192 strings) Co-authored-by: Gareth Coles Co-authored-by: Hosted Weblate Co-authored-by: J. Lavoie Translate-URL: https://hosted.weblate.org/projects/kord-extensions/main/fr/ Translation: Kord Extensions/Primary Translations --- .../kordex/strings_fr_FR.properties | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties index a569b6e807..e78b9f5ee4 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_fr_FR.properties @@ -5,48 +5,39 @@ argumentParser.error.notAllValid=Le paramètre `{0}` a été saisi avec {1} {1, argumentParser.error.unknownConverterType=Type de convertisseur inconnu \: {0}` argumentParser.error.noFilledArguments=Cette commande a {0} {0, plural, \=1 {paramètre} other {paramètres}} requis. argumentParser.error.someFilledArguments=Cette commande a {0} {0, plural, one {} \=1 {paramètre} other {paramètres}} requis, mais seulement {1} ont été rempli. - channelType.dm=MP channelType.groupDm=Groupe MP channelType.guildCategory=Catégorie -channelType.guildNews=Actualités +channelType.guildNews=Annonce channelType.guildStageVoice=Stage -channelType.guildStore=Store -channelType.guildText=Text -channelType.guildVoice=Voice -channelType.publicNewsThread=News Thread -channelType.publicGuildThread=Public Thread -channelType.privateThread=Private Thread -channelType.unknown=Unknown - -checks.responseTemplate=**Error\:** {0} - -checks.inChannel.failed=Must be in **{0}** -checks.notInChannel.failed=Must not be in **{0}** +channelType.guildStore=Magasin +channelType.guildText=Texte +channelType.guildVoice=Voix +channelType.publicNewsThread=Fil d'actualité +channelType.publicGuildThread=Fil de discussion public +channelType.privateThread=Fil de discussion privé +channelType.unknown=Inconnu +checks.responseTemplate=**Erreur :** {0} +checks.inChannel.failed=Doit être dans **{0}** +checks.notInChannel.failed=Ne doit pas être dans **{0}** checks.inCategory.failed=Must be in category\: **{0}** checks.notInCategory.failed=Must not be in category\: **{0}** checks.channelHigher.failed=Must be in a channel higher than **{0}** checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel - checks.anyGuild.failed=Must be in a server checks.noGuild.failed=Must not be in a server checks.inGuild.failed=Must be in server\: **{0}** checks.notInGuild.failed=Must not be in server\: **{0}** - checks.channelType.failed=Must be in a channel of type\: **{0}** checks.notChannelType.failed=Must not be in a channel of type\: **{0}** - checks.hasPermission.failed=Must have permission\: **{0}** checks.notHasPermission.failed=Must not have permission\: **{0}** - checks.isBot.failed=Must be a bot checks.isNotBot.failed=Must not be a bot - checks.isInThread.failed=Must be in a thread checks.isNotInThread.failed=Must not be in a thread - checks.hasRole.failed=Must have role\: **{0}** checks.notHasRole.failed=Must not have role\: **{0}** checks.topRoleEqual.failed=Must have top role\: **{0}** @@ -55,7 +46,6 @@ checks.topRoleHigher.failed=Must have a top role higher than\: **{0}** checks.topRoleLower.failed=Must have a top role lower than\: **{0}** checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role - commands.defaultDescription=Aucune description fournie. commands.error.missingBotPermissions=Je n’ai pas la permission d’exécuter cette commande \!\n\n**Permissions manquantes \:** {0} commands.error.user=Malheureusement, **une erreur s’est produite** lors du traitement de la commande. Veuillez en faire part à un membre de l'équipe. @@ -143,7 +133,6 @@ paginator.button.group.switch=Groupe suivant paginator.button.less=Moins paginator.footer.page=Page {0}/{1} paginator.footer.group=Groupe {0}/{1} - permission.addReactions=Ajouter des réactions permission.administrator=Administrateur permission.all=Toutes les autorisations @@ -182,7 +171,6 @@ permission.useVAD=Utiliser l'activité vocale permission.viewAuditLog=Afficher le journal d'informations permission.viewChannel=Voir le salon permission.viewGuildInsights=Voir les analyses de serveur - utils.message.useThisChannel=Veuillez utiliser {0} pour cette commande. utils.message.commandNotAvailableInDm=Cette commande n'est pas disponible dans les messages privés. utils.colors.black=noires,noire,noirs,noir @@ -194,7 +182,7 @@ utils.colors.white=blancs,blanc utils.colors.yellow=jaunes,jaune utils.durations.ignoredWords=et utils.units.year=a, an, ans -utils.units.month=mo, mois, mois +utils.units.month=mo, mois utils.units.week=s, semaine, semaines utils.units.day=j, jour, jours utils.units.hour=h, heure, heures From c00f937fa7e3c157012d218cf4f19a88192a3576 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Sep 2021 12:12:48 +0200 Subject: [PATCH 066/131] Translated using Weblate (English (United Kingdom)) Currently translated at 100.0% (192 of 192 strings) Co-authored-by: Gareth Coles Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/kord-extensions/main/en_GB/ Translation: Kord Extensions/Primary Translations --- .../kordex/strings_en_GB.properties | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties index d714898225..a4b194fe1b 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_en_GB.properties @@ -5,7 +5,6 @@ argumentParser.error.notAllValid=Argument `{0}` was provided with {1} {1, plural argumentParser.error.unknownConverterType=Unknown converter type provided\: `{0}` argumentParser.error.noFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}. argumentParser.error.someFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}, but only {1} could be filled. - channelType.dm=DM channelType.groupDm=Group DM channelType.guildCategory=Category @@ -18,9 +17,7 @@ channelType.publicNewsThread=News Thread channelType.publicGuildThread=Public Thread channelType.privateThread=Private Thread channelType.unknown=Unknown - checks.responseTemplate=**Error:** {0} - checks.inChannel.failed=Must be in **{0}** checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category: **{0}** @@ -29,24 +26,18 @@ checks.channelHigher.failed=Must be in a channel higher than **{0}** checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel - checks.anyGuild.failed=Must be in a server checks.noGuild.failed=Must not be in a server checks.inGuild.failed=Must be in server: **{0}** checks.notInGuild.failed=Must not be in server: **{0}** - checks.channelType.failed=Must be in a channel of type: **{0}** checks.notChannelType.failed=Must not be in a channel of type: **{0}** - checks.hasPermission.failed=Must have permission: **{0}** checks.notHasPermission.failed=Must not have permission: **{0}** - checks.isBot.failed=Must be a bot checks.isNotBot.failed=Must not be a bot - checks.isInThread.failed=Must be in a thread checks.isNotInThread.failed=Must not be in a thread - checks.hasRole.failed=Must have role: **{0}** checks.notHasRole.failed=Must not have role: **{0}** checks.topRoleEqual.failed=Must have top role: **{0}** @@ -55,12 +46,11 @@ checks.topRoleHigher.failed=Must have a top role higher than: **{0}** checks.topRoleLower.failed=Must have a top role lower than: **{0}** checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role - commands.defaultDescription=No description provided. -commands.error.missingBotPermissions=I don't have the permissions I need to run that command\!\n\n**Missing permissions\:** {0} +commands.error.missingBotPermissions=I don''t have the permissions I need to run that command!\n\n**Missing permissions:** {0} commands.error.user=Unfortunately, **an error occurred** during command processing. Please let a staff member know. -commands.error.user.sentry.message=Unfortunately, **an error occurred** during command processing. If you'd like to submit information on what you were doing when this error happened, please use the following command\: ```{0}feedback {1} ``` -commands.error.user.sentry.slash=Unfortunately, **an error occurred** during command processing. If you'd like to submit information on what you were doing when this error happened, please use the following command\: ```/feedback {0} ``` +commands.error.user.sentry.message=Unfortunately, **an error occurred** during command processing. If you''d like to submit information on what you were doing when this error happened, please use the following command: ```{0}feedback {1} ``` +commands.error.user.sentry.slash=Unfortunately, **an error occurred** during command processing. If you''d like to submit information on what you were doing when this error happened, please use the following command: ```/feedback {0} ``` converters.boolean.signatureType=yes/no converters.boolean.errorType=`yes` or `no` converters.channel.signatureType=channel @@ -143,7 +133,6 @@ paginator.button.group.switch=Next Group paginator.button.less=Less paginator.footer.page=Page {0}/{1} paginator.footer.group=Group {0}/{1} - permission.addReactions=Add Reactions permission.administrator=Administrator permission.all=All Permissions @@ -182,7 +171,6 @@ permission.useVAD=Use Voice Activity permission.viewAuditLog=View Audit Log permission.viewChannel=View Channel permission.viewGuildInsights=View Server Insights - utils.message.useThisChannel=Please use {0} for this command. utils.message.commandNotAvailableInDm=This command is not available via private message. utils.colors.black=black,blck,blk From fea2396d3ea63ce708111e97a97e86f0fe4f197b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Sep 2021 12:12:48 +0200 Subject: [PATCH 067/131] Translated using Weblate (German) Currently translated at 100.0% (192 of 192 strings) Translated using Weblate (German) Currently translated at 100.0% (192 of 192 strings) Translated using Weblate (German) Currently translated at 100.0% (192 of 192 strings) Translated using Weblate (German) Currently translated at 84.3% (162 of 192 strings) Co-authored-by: AppleTheGolden Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/kord-extensions/main/de/ Translation: Kord Extensions/Primary Translations --- .../kordex/strings_de_DE.properties | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties index cec95b893e..3589013bfc 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings_de_DE.properties @@ -5,57 +5,47 @@ argumentParser.error.notAllValid=Das Argument `{0}` wurde mit {1} {1, plural, \= argumentParser.error.unknownConverterType=Unbekannter Konvertertyp\: `{0}` argumentParser.error.noFilledArguments=Dieser Befehl hat {0} {0, plural, \=1 {erforderliches Argument} other {erforderliche Argumente}}. argumentParser.error.someFilledArguments=Dieser Befehl hat {0} {0, plural, \=1 {erforderliches Argument} other {erforderliche Argumente}}, aber nur {1} {1, plural, \=1 {konnte} other {konnten}} ausgefüllt werden. - channelType.dm=DM -channelType.groupDm=Group DM -channelType.guildCategory=Category -channelType.guildNews=News +channelType.groupDm=Gruppenchat +channelType.guildCategory=Kategorie +channelType.guildNews=Ankündigungen channelType.guildStageVoice=Stage channelType.guildStore=Store channelType.guildText=Text -channelType.guildVoice=Voice -channelType.publicNewsThread=News Thread +channelType.guildVoice=Sprache +channelType.publicNewsThread=Ankündigungsthread channelType.publicGuildThread=Public Thread channelType.privateThread=Private Thread -channelType.unknown=Unknown - +channelType.unknown=Unbekannt checks.responseTemplate=**Fehler\:** {0} - -checks.inChannel.failed=Must be in **{0}** -checks.notInChannel.failed=Must not be in **{0}** -checks.inCategory.failed=Must be in category\: **{0}** -checks.notInCategory.failed=Must not be in category\: **{0}** -checks.channelHigher.failed=Must be in a channel higher than **{0}** -checks.channelLower.failed=Must not be in a channel lower than **{0}** -checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel -checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel - -checks.anyGuild.failed=Must be in a server -checks.noGuild.failed=Must not be in a server -checks.inGuild.failed=Must be in server\: **{0}** -checks.notInGuild.failed=Must not be in server\: **{0}** - -checks.channelType.failed=Must be in a channel of type\: **{0}** -checks.notChannelType.failed=Must not be in a channel of type\: **{0}** - -checks.hasPermission.failed=Must have permission\: **{0}** -checks.notHasPermission.failed=Must not have permission\: **{0}** - -checks.isBot.failed=Must be a bot -checks.isNotBot.failed=Must not be a bot - -checks.isInThread.failed=Must be in a thread -checks.isNotInThread.failed=Must not be in a thread - -checks.hasRole.failed=Must have role\: **{0}** -checks.notHasRole.failed=Must not have role\: **{0}** -checks.topRoleEqual.failed=Must have top role\: **{0}** -checks.topRoleNotEqual.failed=Must not have top role\: **{0}** -checks.topRoleHigher.failed=Must have a top role higher than\: **{0}** -checks.topRoleLower.failed=Must have a top role lower than\: **{0}** -checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role -checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role - +checks.inChannel.failed=Muss in **{0}** sein +checks.notInChannel.failed=Darf nicht in **{0}** sein +checks.inCategory.failed=Muss in der Kategorie **{0}** sein +checks.notInCategory.failed=Darf nicht in der Kategorie **{0}** sein +checks.channelHigher.failed=Muss in einem Kanal über **{0}** sein +checks.channelLower.failed=Muss in einem Kanal über **{0}** sein +checks.channelHigherOrEqual.failed=Muss in **{0}** oder einem höheren Kanal sein +checks.channelLowerOrEqual.failed=Muss in **{0}** oder einem niedrigeren Kanal sein +checks.anyGuild.failed=Muss in einem Server sein +checks.noGuild.failed=Darf nicht in einem Server sein +checks.inGuild.failed=Muss im Server **{0}** sein +checks.notInGuild.failed=Darf nicht im Server **{0}** sein +checks.channelType.failed=Muss in einem Kanel des Typs **{0}** sein +checks.notChannelType.failed=Darf nicht in einem Kanal des Typs **{0}** sein +checks.hasPermission.failed=Muss Berechtigung **{0}** haben +checks.notHasPermission.failed=Darf nicht Berechtigung **{0}** haben +checks.isBot.failed=Muss ein Bot sein +checks.isNotBot.failed=Darf kein Bot sein +checks.isInThread.failed=Muss in einem Thread sein +checks.isNotInThread.failed=Darf nicht in einem Thread sein +checks.hasRole.failed=Muss Rolle **{0}** haben +checks.notHasRole.failed=Darf nicht Rolle **{0}**haben +checks.topRoleEqual.failed=Muss als höchste Rolle **{0}** haben +checks.topRoleNotEqual.failed=Darf nicht als höchste Rolle **{0}** haben +checks.topRoleHigher.failed=Muss als höchste Rolle eine Höhere als **{0}** haben +checks.topRoleLower.failed=Muss als höchste Rolle eine Niedrigere als **{0}** haben +checks.topRoleHigherOrEqual.failed=Muss als höchste Rolle **{0}** oder eine Höhere haben +checks.topRoleLowerOrEqual.failed=Muss als höchste Rolle **{0}** oder eine Niedrigere haben commands.defaultDescription=Keine Beschreibung verfügbar. commands.error.missingBotPermissions=Ich habe nicht die Berechtigungen, die ich brauche, um diesen Befehl auszuführen\!\n\n**Fehlende Berechtigungen\:** {0} commands.error.user=Leider **ist ein Fehler passiert** während der Befehl verarbeitet wurde. Bitte informiere deine Server-Administration. @@ -143,7 +133,6 @@ paginator.button.group.switch=Nächste Gruppe paginator.button.less=Weniger paginator.footer.page=Seite {0}/{1} paginator.footer.group=Gruppe {0}/{1} - permission.addReactions=Reaktionen hinzufügen permission.administrator=Administrator permission.all=Alle Berechtigungen @@ -152,8 +141,8 @@ permission.banMembers=Mitglieder bannen permission.changeNickname=Nickname ändern permission.connect=Verbinden (Sprachkanal) permission.createInstantInvite=Einladung erstellen -permission.createPrivateThreads=Create Private Threads -permission.createPublicThreads=Create Public Threads +permission.createPrivateThreads=Private Threads erstellen +permission.createPublicThreads=Öffentliche Threads erstellen permission.deafenMembers=Ausgabe von Mitgliedern deaktivieren permission.embedLinks=Links einbetten permission.kickMembers=Mitglieder kicken @@ -182,7 +171,6 @@ permission.useVAD=Sprachaktivierung verwenden permission.viewAuditLog=Audit-Log einsehen permission.viewChannel=Kanäle ansehen permission.viewGuildInsights=Server-Einblicke anzeigen - utils.message.useThisChannel=Verwende für diesen Befehl bitte {0}. utils.message.commandNotAvailableInDm=Dieser Befehl ist nicht via Direktnachricht verfügbar. utils.colors.black=schwarz,schwrz From 24203cc429e89664d818e0dd2fabbebba07716da Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 15 Sep 2021 16:23:54 +0100 Subject: [PATCH 068/131] Trace logging for application command perms registration --- .../commands/application/ApplicationCommandRegistry.kt | 4 ++++ 1 file changed, 4 insertions(+) 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 f2b7c0dd91..583a55c5f9 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 @@ -157,6 +157,8 @@ public open class ApplicationCommandRegistry : KoinComponent { } } } + + logger.trace { "Applied permissions for ${commands.size} commands." } } else { logger.warn { "Applying permissions to global application commands is currently not supported." } } @@ -395,6 +397,8 @@ public open class ApplicationCommandRegistry : KoinComponent { command.allowedRoles.map { role(it, true) } command.disallowedRoles.map { role(it, false) } } + + logger.trace { "Applied permissions for command: ${command.name} ($command)" } } else { logger.warn { "Applying permissions to global application commands is currently not supported." } } From 9211d8a15905c219153c49d90c4345d0c4b0ee07 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 14:44:59 +0100 Subject: [PATCH 069/131] Allow for custom Kord instances (@ByteAlex) --- .../kord/extensions/ExtensibleBot.kt | 4 +-- .../builders/ExtensibleBotBuilder.kt | 27 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 7184a45152..616f171f55 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -71,7 +71,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva /** @suppress Function that sets up the bot early on, called by the builder. **/ public open suspend fun setup() { - val kord = Kord(token) { + val kord = settings.kordBuilder(token) { cache { settings.cacheBuilder.builder.invoke(this, it) } @@ -88,7 +88,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva enableShutdownHook = settings.hooksBuilder.kordShutdownHook - settings.kordBuilders.forEach { it() } + settings.kordHooks.forEach { it() } } loadModule { single { kord } bind Kord::class } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 7acd069a67..18c1c6c419 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -94,7 +94,12 @@ public open class ExtensibleBotBuilder { public val applicationCommandsBuilder: ApplicationCommandsBuilder = ApplicationCommandsBuilder() /** @suppress List of Kord builders, shouldn't be set directly by the user. **/ - public val kordBuilders: MutableList Unit> = mutableListOf() + public val kordHooks: MutableList Unit> = mutableListOf() + + /** @suppress Kord builder, creates a Kord instance. **/ + public var kordBuilder: suspend (String, suspend KordBuilder.() -> Unit) -> Kord = { token, builder -> + Kord(token) { builder() } + } /** Logging level Koin should use, defaulting to ERROR. **/ public var koinLogLevel: Level = Level.ERROR @@ -130,8 +135,8 @@ public open class ExtensibleBotBuilder { } /** - * DSL function allowing for additional Kord builders to be specified, allowing for direct customisation of the - * Kord object. + * DSL function allowing for additional Kord configuration builders to be specified, allowing for direct + * customisation of the Kord object. * * Multiple builders may be registered, and they'll be called in the order they were registered here. Builders are * called after Kord Extensions has applied its own builder actions - so you can override the changes it makes here @@ -141,7 +146,19 @@ public open class ExtensibleBotBuilder { */ @BotBuilderDSL public fun kord(builder: suspend KordBuilder.() -> Unit) { - kordBuilders.add(builder) + kordHooks.add(builder) + } + + /** + * Function allowing you to specify a callable that constructs and returns a Kord instance. This can be used + * to specify your own Kord subclass, if you need to - but shouldn't be a replacement for registering a [kord] + * configuration builder. + * + * @see Kord + */ + @BotBuilderDSL + public fun customKordBuilder(builder: suspend (String, suspend KordBuilder.() -> Unit) -> Kord) { + kordBuilder = builder } /** @@ -300,7 +317,7 @@ public open class ExtensibleBotBuilder { * Number of messages to keep in the cache. Defaults to 10,000. * * To disable automatic configuration of the message cache, set this to `null` or `0`. You can configure the - * cache yourself using the [kord] function, and interact with the resulting [DataCache] object using the + * cache yourself using the [kordHook] function, and interact with the resulting [DataCache] object using the * [transformCache] function. */ @Suppress("MagicNumber") From db59007ecc2aeb398699115d8a5d1229429040ec Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 17:32:38 +0100 Subject: [PATCH 070/131] [#79] Some message awaiting functions --- .../kord/extensions/utils/_Message.kt | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt index a66c6874f5..c617d88f1d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt @@ -4,7 +4,10 @@ import com.kotlindiscord.kord.extensions.commands.CommandContext import dev.kord.common.entity.DiscordPartialMessage import dev.kord.common.entity.MessageFlag import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord import dev.kord.core.behavior.MessageBehavior +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.reply import dev.kord.core.cache.data.MessageData @@ -372,3 +375,92 @@ public val Message.isUrgent: Boolean public val Message.isEphemeral: Boolean get() = data.flags.value?.contains(MessageFlag.Ephemeral) == true + +/** + * Wait for a message, using the given timeout (in seconds) and filter function. + * + * Will return `null` if no message is found before the timeout. + */ +public suspend fun waitForMessage( + timeout: Long, + filter: (suspend (MessageCreateEvent).() -> Boolean) = { true } +): Message? { + val kord = getKoin().get() + val event = kord.waitFor(timeout, filter) + + return event?.message +} + +/** + * Wait for a message from a user, using the given timeout (in seconds) and extra filter function. + * + * Will return `null` if no message is found before the timeout. + */ +public suspend fun UserBehavior.waitForMessage( + timeout: Long, + filter: (suspend (MessageCreateEvent).() -> Boolean) = { true } +): Message? { + val kord = getKoin().get() + val event = kord.waitFor(timeout) { + message.author?.id == id && + filter() + } + + return event?.message +} + +/** + * Wait for a message in this channel, using the given timeout (in seconds) and extra filter function. + * + * Will return `null` if no message is found before the timeout. + */ +public suspend fun MessageChannelBehavior.waitForMessage( + timeout: Long, + filter: (suspend (MessageCreateEvent).() -> Boolean) = { true } +): Message? { + val kord = getKoin().get() + val event = kord.waitFor(timeout) { + message.channelId == id && + filter() + } + + return event?.message +} + +/** + * Wait for a message in reply to this one, using the given timeout (in seconds) and extra filter function. + * + * Will return `null` if no message is found before the timeout. + */ +public suspend fun MessageBehavior.waitForReply( + timeout: Long, + filter: (suspend (MessageCreateEvent).() -> Boolean) = { true } +): Message? { + val kord = getKoin().get() + val event = kord.waitFor(timeout) { + message.messageReference?.message?.id == id && + filter() + } + + return event?.message +} + +/** + * Wait for a message by the user that invoked this command, in the channel it was invoked in, using the given + * timeout (in seconds) and extra filter function. + * + * Will return `null` if no message is found before the timeout. + */ +public suspend fun CommandContext.waitForResponse( + timeout: Long, + filter: (suspend (MessageCreateEvent).() -> Boolean) = { true } +): Message? { + val kord = com.kotlindiscord.kord.extensions.utils.getKoin().get() + val event = kord.waitFor(timeout) { + message.author?.id == getUser()?.id && + message.channelId == getChannel()?.id && + filter() + } + + return event?.message +} From def18c1afb476d86274f84960af2f9cfec4840ec Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 17:33:19 +0100 Subject: [PATCH 071/131] Launch event handlers in their own context (@ByteAlex) --- .../kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 616f171f55..a3cda89c82 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -99,7 +99,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva addDefaultExtensions() kord.on { - kord.launch { + this.launch { send(this@on) } } From 05d5da1bb52444f89a9dcfc0867f3092161f7a97 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 19:02:36 +0100 Subject: [PATCH 072/131] ReadyEvent no longer required for application commands --- .../kord/extensions/ExtensibleBot.kt | 32 +++++++------------ .../application/ApplicationCommandRegistry.kt | 4 +++ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index a3cda89c82..c33e8affa2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -95,14 +95,14 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva settings.cacheBuilder.dataCacheBuilder.invoke(kord, kord.cache) - registerListeners() - addDefaultExtensions() - kord.on { this.launch { send(this@on) } } + + registerListeners() + addDefaultExtensions() } /** Start up the bot and log into Discord. **/ @@ -132,23 +132,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva logger.warn { "Disconnected: $closeCode" } } - on { - if (!initialized) { // We do this because a reconnection will cause this event to happen again. - initialized = true - - if (settings.applicationCommandsBuilder.enabled) { - getKoin().get().initialRegistration() - } else { - logger.info { - "Application command support is disabled - set `enabled` to `true` in the " + - "`applicationCommands` builder if you want to use them." - } - } - } - - logger.info { "Ready!" } - } - on { getKoin().get().handle(this) } @@ -162,7 +145,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva getKoin().get().handleEvent(this) } } else { - logger.info { + logger.debug { "Chat command support is disabled - set `enabled` to `true` in the `chatCommands` builder" + " if you want to use them." } @@ -180,6 +163,13 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva on { getKoin().get().handle(this) } + + getKoin().get().initialRegistration() + } else { + logger.debug { + "Application command support is disabled - set `enabled` to `true` in the " + + "`applicationCommands` builder if you want to use them." + } } } 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 583a55c5f9..b9715a2b19 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 @@ -70,6 +70,10 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Handles the initial registration of commands, after extensions have been loaded. **/ public open suspend fun initialRegistration() { + if (initialised) { + return + } + if (!bot.settings.applicationCommandsBuilder.register) { logger.debug { "Application command registration is disabled, pairing existing commands with extension commands" From 5411a2a35385f752140a8f31221c9df181609cb9 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 19:05:34 +0100 Subject: [PATCH 073/131] Fix registration ordering issue --- .../com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index c33e8affa2..ee626ae8a7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -95,20 +95,21 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva settings.cacheBuilder.dataCacheBuilder.invoke(kord, kord.cache) + addDefaultExtensions() + kord.on { this.launch { send(this@on) } } - - registerListeners() - addDefaultExtensions() } /** Start up the bot and log into Discord. **/ public open suspend fun start() { settings.hooksBuilder.runBeforeStart(this) + registerListeners() + getKoin().get().login(settings.presenceBuilder) } From 3aed3ea2b0db08fba4ee5fecd3b937e09d82c409 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 19:16:21 +0100 Subject: [PATCH 074/131] Fix waitFor message comments --- .../kotlindiscord/kord/extensions/utils/_Message.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt index c617d88f1d..a3c943a0c6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt @@ -377,7 +377,7 @@ public val Message.isEphemeral: Boolean data.flags.value?.contains(MessageFlag.Ephemeral) == true /** - * Wait for a message, using the given timeout (in seconds) and filter function. + * Wait for a message, using the given timeout (in milliseconds ) and filter function. * * Will return `null` if no message is found before the timeout. */ @@ -392,7 +392,7 @@ public suspend fun waitForMessage( } /** - * Wait for a message from a user, using the given timeout (in seconds) and extra filter function. + * Wait for a message from a user, using the given timeout (in milliseconds) and extra filter function. * * Will return `null` if no message is found before the timeout. */ @@ -410,7 +410,7 @@ public suspend fun UserBehavior.waitForMessage( } /** - * Wait for a message in this channel, using the given timeout (in seconds) and extra filter function. + * Wait for a message in this channel, using the given timeout (in milliseconds) and extra filter function. * * Will return `null` if no message is found before the timeout. */ @@ -428,7 +428,7 @@ public suspend fun MessageChannelBehavior.waitForMessage( } /** - * Wait for a message in reply to this one, using the given timeout (in seconds) and extra filter function. + * Wait for a message in reply to this one, using the given timeout (in milliseconds) and extra filter function. * * Will return `null` if no message is found before the timeout. */ @@ -447,7 +447,7 @@ public suspend fun MessageBehavior.waitForReply( /** * Wait for a message by the user that invoked this command, in the channel it was invoked in, using the given - * timeout (in seconds) and extra filter function. + * timeout (in milliseconds) and extra filter function. * * Will return `null` if no message is found before the timeout. */ From 1c34989df33cbc37857633bbb95c0aea5b50f3df Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 19:35:38 +0100 Subject: [PATCH 075/131] More event-specific launching --- .../kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index ee626ae8a7..2861d14900 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -196,7 +196,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva * @param scope Coroutine scope to run the body of your callback under. * @param consumer The callback to run when the event is fired. */ - public inline fun on( + public inline fun on( launch: Boolean = true, scope: CoroutineScope = this.getKoin().get(), noinline consumer: suspend T.() -> Unit @@ -205,7 +205,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva .filterIsInstance() .onEach { runCatching { - if (launch) scope.launch { consumer(it) } else consumer(it) + if (launch) it.launch { consumer(it) } else consumer(it) }.onFailure { logger.catching(it) } }.catch { logger.catching(it) } .launchIn(scope) From 282a37c5270e05769e8f6b12aa69421353494b02 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 20:06:49 +0100 Subject: [PATCH 076/131] More quick-fixes --- .../com/kotlindiscord/kord/extensions/commands/Command.kt | 2 +- .../commands/application/ApplicationCommandContext.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt index 053c120d42..97a48a3e3d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt @@ -37,7 +37,7 @@ public abstract class Command(public val extension: Extension) { /** Quick shortcut for emitting a command event without blocking. **/ public open suspend fun emitEventAsync(event: CommandEvent<*, *>): Job = - event.kord.launch { + event.launch { extension.bot.send(event) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt index 7036e0a7d7..2c65cd606b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -55,7 +55,7 @@ public abstract class ApplicationCommandContext( /** Extract member information from event data, if that context is available. **/ public override suspend fun getMember(): MemberBehavior? = - guild?.getMember(genericEvent.interaction.user.id) + guild?.getMemberOrNull(genericEvent.interaction.user.id) /** Extract user information from event data, if that context is available. **/ public override suspend fun getUser(): UserBehavior = From f65d82870f552cb55e64e1ee7443655b042c4f65 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 20:49:15 +0100 Subject: [PATCH 077/131] Move to behaviors for command contexts and paginators --- .../application/ApplicationCommandContext.kt | 7 +++---- .../commands/chat/ChatCommandContext.kt | 20 +++++++++---------- .../pagination/BaseButtonPaginator.kt | 4 ++-- .../extensions/pagination/BasePaginator.kt | 4 ++-- .../pagination/EphemeralResponsePaginator.kt | 4 ++-- .../pagination/MessageButtonPaginator.kt | 4 ++-- .../pagination/PublicFollowUpPaginator.kt | 4 ++-- .../pagination/PublicResponsePaginator.kt | 4 ++-- .../pagination/builders/PaginatorBuilder.kt | 4 ++-- 9 files changed, 27 insertions(+), 28 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt index 2c65cd606b..1626c00d59 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -6,7 +6,6 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent import org.koin.core.component.inject @@ -47,15 +46,15 @@ public abstract class ApplicationCommandContext( /** Extract channel information from event data, if that context is available. **/ public override suspend fun getChannel(): MessageChannelBehavior = - genericEvent.interaction.getChannel() + genericEvent.interaction.channel /** Extract guild information from event data, if that context is available. **/ public override suspend fun getGuild(): GuildBehavior? = - (channel as? GuildMessageChannel)?.guild + genericEvent.interaction.data.guildId.value ?.let { GuildBehavior(it, genericEvent.kord) } /** Extract member information from event data, if that context is available. **/ public override suspend fun getMember(): MemberBehavior? = - guild?.getMemberOrNull(genericEvent.interaction.user.id) + getGuild()?.let { MemberBehavior(it.id, genericEvent.interaction.user.id, genericEvent.kord) } /** Extract user information from event data, if that context is available. **/ public override suspend fun getUser(): UserBehavior = diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index ba2af247cb..cb01ce41a2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -11,11 +11,11 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.MemberBehavior +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior -import dev.kord.core.entity.Guild -import dev.kord.core.entity.Member import dev.kord.core.entity.Message -import dev.kord.core.entity.User import dev.kord.core.event.message.MessageCreateEvent /** @@ -40,13 +40,13 @@ public open class ChatCommandContext( public open lateinit var channel: MessageChannelBehavior /** Guild this command happened in, if any. **/ - public open var guild: Guild? = null + public open var guild: GuildBehavior? = null /** Guild member responsible for executing this command, if any. **/ - public open var member: Member? = null + public open var member: MemberBehavior? = null /** User responsible for executing this command, if any (if `null`, it's a webhook). **/ - public open var user: User? = null + public open var user: UserBehavior? = null /** Message object containing this command invocation. **/ public open lateinit var message: Message @@ -68,10 +68,10 @@ public open class ChatCommandContext( arguments = args } - override suspend fun getChannel(): MessageChannelBehavior = event.message.channel.asChannel() - override suspend fun getGuild(): Guild? = event.getGuild() - override suspend fun getMember(): Member? = event.message.getAuthorAsMember() - override suspend fun getUser(): User? = event.message.author + override suspend fun getChannel(): MessageChannelBehavior = event.message.channel + override suspend fun getGuild(): GuildBehavior? = event.guildId?.let { GuildBehavior(it, event.kord) } + override suspend fun getMember(): MemberBehavior? = event.member + override suspend fun getUser(): UserBehavior? = event.message.author /** Extract message information from event data, if that context is available. **/ public open suspend fun getMessage(): Message = event.message diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index 268bd33139..c0b1f4f8ad 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -14,8 +14,8 @@ import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ButtonStyle +import dev.kord.core.behavior.UserBehavior import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import java.util.* @@ -24,7 +24,7 @@ import java.util.* */ public abstract class BaseButtonPaginator( pages: Pages, - owner: User? = null, + owner: UserBehavior? = null, timeoutSeconds: Long? = null, keepEmbed: Boolean = true, switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt index e81a9ee9e0..8c709128c4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BasePaginator.kt @@ -5,8 +5,8 @@ import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.core.Kord +import dev.kord.core.behavior.UserBehavior import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.rest.builder.message.EmbedBuilder import mu.KotlinLogging import org.koin.core.component.KoinComponent @@ -52,7 +52,7 @@ public val EXPAND_EMOJI: ReactionEmoji.Unicode = ReactionEmoji.Unicode("\u2139\u */ public abstract class BasePaginator( public open val pages: Pages, - public open val owner: User? = null, + public open val owner: UserBehavior? = null, public open val timeoutSeconds: Long? = null, public open val keepEmbed: Boolean = true, public open val switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt index 048e182a71..b1832f3234 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt @@ -5,10 +5,10 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.rest.builder.message.modify.embed import java.util.* @@ -19,7 +19,7 @@ import java.util.* */ public class EphemeralResponsePaginator( pages: Pages, - owner: User? = null, + owner: UserBehavior? = null, timeoutSeconds: Long? = null, switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, bundle: String? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt index 0d572aa15a..00eab34b18 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/MessageButtonPaginator.kt @@ -5,12 +5,12 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.rest.builder.message.create.allowedMentions import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.allowedMentions @@ -26,7 +26,7 @@ import java.util.* */ public class MessageButtonPaginator( pages: Pages, - owner: User? = null, + owner: UserBehavior? = null, timeoutSeconds: Long? = null, keepEmbed: Boolean = true, switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt index a90d624056..f2d9894a3c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt @@ -5,11 +5,11 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed @@ -23,7 +23,7 @@ import java.util.* */ public class PublicFollowUpPaginator( pages: Pages, - owner: User? = null, + owner: UserBehavior? = null, timeoutSeconds: Long? = null, keepEmbed: Boolean = true, switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt index b208c8016a..c0255b927d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt @@ -5,10 +5,10 @@ package com.kotlindiscord.kord.extensions.pagination import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.rest.builder.message.modify.embed import java.util.* @@ -19,7 +19,7 @@ import java.util.* */ public class PublicResponsePaginator( pages: Pages, - owner: User? = null, + owner: UserBehavior? = null, timeoutSeconds: Long? = null, keepEmbed: Boolean = true, switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt index bc4c6e6686..7da66f8cec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/builders/PaginatorBuilder.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.pagination.builders import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import dev.kord.core.behavior.UserBehavior import dev.kord.core.entity.ReactionEmoji -import dev.kord.core.entity.User import dev.kord.rest.builder.message.EmbedBuilder import java.util.* @@ -22,7 +22,7 @@ public class PaginatorBuilder( public val pages: Pages = Pages(defaultGroup) /** Paginator owner, if only one person should be able to interact. **/ - public var owner: User? = null + public var owner: UserBehavior? = null /** Paginator timeout, in seconds. When elapsed, the paginator will be destroyed. **/ public var timeoutSeconds: Long? = null From ccb0168c44fe592566f8ed19a5930ad773025ba9 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 18 Sep 2021 21:34:08 +0100 Subject: [PATCH 078/131] More cache-hits removed --- .../application/ApplicationCommand.kt | 21 ++++++++++++++----- .../application/slash/SlashCommand.kt | 4 ++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 47a7e9599b..4f6b5bdcea 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -16,7 +16,6 @@ import dev.kord.core.Kord import dev.kord.core.any import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior -import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.InteractionCreateEvent import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -216,11 +215,23 @@ public abstract class ApplicationCommand( // Handle discord-side perms checks, as they can't be relied on to enforce them - val channel = event.interaction.channel.asChannelOrNull() as? GuildMessageChannel ?: return allowByDefault - val member = event.interaction.user.asMember(channel.guildId) + val guildId = event.interaction.data.guildId.value ?: return allowByDefault + val memberId = event.interaction.user.id - val isAllowed = member.id in allowedUsers || member.roles.any { it.id in allowedRoles } - val isDenied = member.id in disallowedUsers || member.roles.any { it.id in disallowedRoles } + var isAllowed = memberId in allowedUsers + var isDenied = memberId in disallowedUsers + + if (allowedRoles.isNotEmpty()) { + val member = GuildBehavior(guildId, kord).getMember(memberId) + + isAllowed = isAllowed || member.roles.any { it.id in allowedRoles } + } + + if (disallowedRoles.isNotEmpty()) { + val member = GuildBehavior(guildId, kord).getMember(memberId) + + isDenied = isDenied || member.roles.any { it.id in disallowedRoles } + } return if (allowByDefault) { !isDenied diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 62bf1c9dfb..2c30270099 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -111,6 +111,10 @@ public abstract class SlashCommand, A : Arguments> /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { + if (requiredPerms.isEmpty()) { + return // Nothing to check, don't try to hit the cache + } + if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) .permissionsForMember(kord.selfId) From 808dc0376b6b121381b30af8d765a33c18b7ca68 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 19 Sep 2021 09:52:47 +0100 Subject: [PATCH 079/131] [#81] `env` now throws, add `envOrNull` --- .../kord/extensions/utils/_Environment.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt index 7a38f359ee..548dd0d017 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Environment.kt @@ -22,7 +22,7 @@ private val envMap: MutableMap = mutableMapOf() * @param name Environmental variable to get the value for. * @return The value of the environmental variable, or `null` if it doesn't exist. */ -public fun env(name: String): String? { +public fun envOrNull(name: String): String? { if (firstLoad) { firstLoad = false @@ -73,3 +73,23 @@ public fun env(name: String): String? { return envMap[name] ?: System.getenv()[name] } + +/** + * Returns the value of an environmental variable, loading from a `.env` file in the current working directory if + * possible. + * + * This function caches the contents of the `.env` file the first time it's called - there's no way to parse the file + * again later. + * + * This function will throw an exception if the environmental variable can't be found. + * + * @param name Environmental variable to get the value for. + * + * @throws RuntimeException Thrown if the environmental variable can't be found. + * @return The value of the environmental variable. + */ +public fun env(name: String): String = + envOrNull(name) ?: error( + "Missing environmental variable '$name' - please set this by adding it to a `.env` file, or using your" + + "system or process manager's environment management commands and tools." + ) From 0c1382fd5df88e117483d0ec04830b21abbaf750 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 19 Sep 2021 10:11:04 +0100 Subject: [PATCH 080/131] Unregistering application commands can now delete them from Discord --- .../application/ApplicationCommandRegistry.kt | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) 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 b9715a2b19..ac84fcd46b 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 @@ -526,39 +526,71 @@ public open class ApplicationCommandRegistry : KoinComponent { // region: Unregistration functions /** Unregister a message command. **/ - public open suspend fun unregisterGeneric(command: ApplicationCommand<*>): ApplicationCommand<*>? = + public open suspend fun unregisterGeneric( + command: ApplicationCommand<*>, + delete: Boolean = true + ): ApplicationCommand<*>? = when (command) { - is MessageCommand<*> -> unregister(command) - is SlashCommand<*, *> -> unregister(command) - is UserCommand<*> -> unregister(command) + is MessageCommand<*> -> unregister(command, delete) + is SlashCommand<*, *> -> unregister(command, delete) + is UserCommand<*> -> unregister(command, delete) else -> error("Unsupported application command type: ${command.type.name}") } /** Unregister a message command. **/ - public open suspend fun unregister(command: MessageCommand<*>): MessageCommand<*>? { + public open suspend fun unregister(command: MessageCommand<*>, delete: Boolean = true): MessageCommand<*>? { val filtered = messageCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null + if (delete) { deleteCommandGeneric(command, id) } + return messageCommands.remove(id) } /** Unregister a slash command. **/ - public open suspend fun unregister(command: SlashCommand<*, *>): SlashCommand<*, *>? { + public open suspend fun unregister(command: SlashCommand<*, *>, delete: Boolean = true): SlashCommand<*, *>? { val filtered = slashCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null + if (delete) { deleteCommandGeneric(command, id) } + return slashCommands.remove(id) } /** Unregister a user command. **/ - public open suspend fun unregister(command: UserCommand<*>): UserCommand<*>? { + public open suspend fun unregister(command: UserCommand<*>, delete: Boolean = true): UserCommand<*>? { val filtered = userCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null + if (delete) { deleteCommandGeneric(command, id) } + return userCommands.remove(id) } + /** @suppress Internal function used to delete the given command from Discord. Used by [unregister]. **/ + public open suspend fun deleteCommandGeneric( + command: ApplicationCommand<*>, + discordCommandId: Snowflake, + ) { + try { + if (command.guildId != null) { + kord.unsafe.guildApplicationCommand(command.guildId!!, kord.resources.applicationId, discordCommandId) + } else { + kord.unsafe.globalApplicationCommand(kord.resources.applicationId, discordCommandId).delete() + } + } catch (e: KtorRequestException) { + logger.warn(e) { + "Failed to delete ${command.type.name} command ${command.name}" + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + } + } + // endregion // region: Event handlers From 5b49e6d8ed3387ffdd5bb05075876021cf6f8251 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 19 Sep 2021 13:55:59 +0100 Subject: [PATCH 081/131] Unregistering application commands can now delete them from Discord --- .../commands/application/ApplicationCommandRegistry.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 ac84fcd46b..012c4680fc 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 @@ -543,7 +543,7 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = messageCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteCommandGeneric(command, id) } + if (delete) { deleteGeneric(command, id) } return messageCommands.remove(id) } @@ -553,7 +553,7 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = slashCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteCommandGeneric(command, id) } + if (delete) { deleteGeneric(command, id) } return slashCommands.remove(id) } @@ -563,13 +563,13 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = userCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteCommandGeneric(command, id) } + if (delete) { deleteGeneric(command, id) } return userCommands.remove(id) } /** @suppress Internal function used to delete the given command from Discord. Used by [unregister]. **/ - public open suspend fun deleteCommandGeneric( + public open suspend fun deleteGeneric( command: ApplicationCommand<*>, discordCommandId: Snowflake, ) { From cae3cdeb35e39c3a425987fcf978968155ac05a0 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 19 Sep 2021 14:55:56 +0100 Subject: [PATCH 082/131] [#82] KordEx core should build for Java 8 --- kord-extensions/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts index 782ab8c037..00608bf1fc 100644 --- a/kord-extensions/build.gradle.kts +++ b/kord-extensions/build.gradle.kts @@ -61,8 +61,8 @@ val javadocJar = task("javadocJar", Jar::class) { } java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlin { @@ -71,7 +71,7 @@ kotlin { tasks.withType().configureEach { kotlinOptions { - jvmTarget = "9" + jvmTarget = "1.8" } } From b75ce0b452792ed91521adf6db685d1e6bcefbef Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sun, 19 Sep 2021 16:32:11 +0100 Subject: [PATCH 083/131] Fix bad Sentry submissions for application commands --- .../extensions/commands/application/message/MessageCommand.kt | 3 --- .../kord/extensions/commands/application/slash/SlashCommand.kt | 3 --- .../kord/extensions/commands/application/user/UserCommand.kt | 3 --- 3 files changed, 9 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index fc7c0fd86c..3c45699983 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -16,7 +16,6 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging @@ -162,8 +161,6 @@ public abstract class MessageCommand>( tag("command", name) tag("extension", extension.name) - - Sentry.captureException(t, "Message command execution failed.") } logger.info { "Error submitted to Sentry: $sentryId" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 2c30270099..4114501de2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -18,7 +18,6 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging @@ -224,8 +223,6 @@ public abstract class SlashCommand, A : Arguments> tag("command", commandObj.name) tag("extension", commandObj.extension.name) - - Sentry.captureException(t, "Slash command execution failed.") } logger.info { "Error submitted to Sentry: $sentryId" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 93c07afe8c..f6d19564b3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -16,7 +16,6 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent -import io.sentry.Sentry import mu.KLogger import mu.KotlinLogging @@ -162,8 +161,6 @@ public abstract class UserCommand>( tag("command", name) tag("extension", extension.name) - - Sentry.captureException(t, "User command execution failed.") } logger.info { "Error submitted to Sentry: $sentryId" } From ebeb0b109ab5b72ca5fa2719927eb2398fa779b6 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 21 Sep 2021 13:59:55 +0100 Subject: [PATCH 084/131] requirePermissions -> requireBotPermissions --- .../kord/extensions/commands/application/ApplicationCommand.kt | 2 +- .../kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt | 2 +- .../kord/extensions/components/ComponentWithAction.kt | 2 +- .../com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 4f6b5bdcea..3d082113c3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -103,7 +103,7 @@ public abstract class ApplicationCommand( } /** If your bot requires permissions to be able to execute the command, add them using this function. **/ - public fun requirePermissions(vararg perms: Permission) { + public fun requireBotPermissions(vararg perms: Permission) { perms.forEach { requiredPerms.add(it) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 48512f174c..7e086319cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -212,7 +212,7 @@ public open class ChatCommand( } /** If your bot requires permissions to be able to execute the command, add them using this function. **/ - public fun requirePermissions(vararg perms: Permission) { + public fun requireBotPermissions(vararg perms: Permission) { perms.forEach { requiredPerms.add(it) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index bfd9c91a8c..1ede9928f7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -77,7 +77,7 @@ public abstract class ComponentWithAction Date: Tue, 21 Sep 2021 18:37:50 +0100 Subject: [PATCH 085/131] Small fixes raised while writing docs --- .../commands/application/ApplicationCommand.kt | 14 ++++++-------- .../extensions/commands/chat/ChatCommandContext.kt | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 3d082113c3..bd6cb5c1de 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -15,6 +15,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.any import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.RoleBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.event.interaction.InteractionCreateEvent import org.koin.core.component.KoinComponent @@ -123,25 +124,22 @@ public abstract class ApplicationCommand( } /** Register an allowed role, and set [allowByDefault] to `false`. **/ - public open fun allowRole(role: Snowflake) { + public open fun allowRole(role: Snowflake): Boolean { allowByDefault = false - allowedRoles.add(role) + return allowedRoles.add(role) } /** Register an allowed role, and set [allowByDefault] to `false`. **/ - public open fun allowRole(role: UserBehavior): Unit = + public open fun allowRole(role: RoleBehavior): Boolean = allowRole(role.id) /** Register a disallowed role, and set [allowByDefault] to `false`. **/ - public open fun disallowRole(role: Snowflake) { - allowByDefault = false - + public open fun disallowRole(role: Snowflake): Boolean = disallowedRoles.add(role) - } /** Register a disallowed role, and set [allowByDefault] to `false`. **/ - public open fun disallowRole(role: UserBehavior): Unit = + public open fun disallowRole(role: RoleBehavior): Boolean = disallowRole(role.id) /** Register an allowed user, and set [allowByDefault] to `false`. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index cb01ce41a2..279054c9cf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -30,7 +30,7 @@ public open class ChatCommandContext( public val messageCommand: ChatCommand, eventObj: MessageCreateEvent, commandName: String, - public open val parser: StringParser?, + public open val parser: StringParser, public val argString: String ) : CommandContext(messageCommand, eventObj, commandName) { /** Event that triggered this command execution. **/ @@ -118,5 +118,5 @@ public open class ChatCommandContext( key: String, replacements: Array = arrayOf(), useReply: Boolean = true - ): Message = respond(translate(key, replacements), useReply) + ): Message = respond(translate(key, command.extension.bundle, replacements), useReply) } From c704894fc677a83ee274718410f30b21433cc343 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 21 Sep 2021 18:51:22 +0100 Subject: [PATCH 086/131] Checks always respond ephemerally now --- .../commands/application/message/PublicMessageCommand.kt | 3 ++- .../commands/application/slash/PublicSlashCommand.kt | 3 ++- .../extensions/commands/application/user/PublicUserCommand.kt | 3 ++- .../extensions/components/buttons/PublicInteractionButton.kt | 3 ++- .../kord/extensions/components/menus/PublicSelectMenu.kt | 3 ++- .../extensions/modules/unsafe/commands/UnsafeUserCommand.kt | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index e41df749c8..8a220c5aac 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandInv import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder @@ -44,7 +45,7 @@ public class PublicMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { content = e.reason } emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index e54311f4a9..7140622ade 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand @@ -75,7 +76,7 @@ public class PublicSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { content = e.reason } emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 35da0bbc7d..e898b7df38 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandInvoca import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.interactions.respond +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder @@ -44,7 +45,7 @@ public class PublicUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { content = e.reason } emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 7cfcf9f3f1..65e64d86e5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.interactions.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.ButtonStyle +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder @@ -44,7 +45,7 @@ public open class PublicInteractionButton( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { content = e.reason } return } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt index f418c336af..0b1dc00845 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -5,6 +5,7 @@ package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.interactions.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder @@ -30,7 +31,7 @@ public open class PublicSelectMenu(timeoutTask: Task?) : SelectMenu Date: Wed, 22 Sep 2021 11:41:36 +0100 Subject: [PATCH 087/131] [#80] Built-in automatic intents --- .../kord/extensions/ExtensibleBot.kt | 3 +- .../builders/ExtensibleBotBuilder.kt | 35 ++++++++++++++++--- .../commands/chat/ChatCommandRegistry.kt | 3 ++ .../kord/extensions/extensions/Extension.kt | 4 +++ .../kord/extensions/extensions/_Commands.kt | 5 +++ .../kord/extensions/extensions/_Events.kt | 10 ++++++ 6 files changed, 53 insertions(+), 7 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 2861d14900..e3ed8298f6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -95,8 +95,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva settings.cacheBuilder.dataCacheBuilder.invoke(kord, kord.cache) - addDefaultExtensions() - kord.on { this.launch { send(this@on) @@ -177,6 +175,7 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva /** This function adds all of the default extensions when the bot is being set up. **/ public open suspend fun addDefaultExtensions() { val extBuilder = settings.extensionsBuilder + if (extBuilder.helpExtensionBuilder.enableBundledExtension) { this.addExtension(::HelpExtension) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 18c1c6c419..d16f1a3ef7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -17,6 +17,7 @@ import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations import com.kotlindiscord.kord.extensions.i18n.SupportedLocales import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import com.kotlindiscord.kord.extensions.utils.getKoin import com.kotlindiscord.kord.extensions.utils.loadModule import dev.kord.cache.api.DataCache import dev.kord.common.Color @@ -76,7 +77,15 @@ public open class ExtensibleBotBuilder { public val i18nBuilder: I18nBuilder = I18nBuilder() /** @suppress Builder that shouldn't be set directly by the user. **/ - public var intentsBuilder: (Intents.IntentsBuilder.() -> Unit)? = null + public var intentsBuilder: (Intents.IntentsBuilder.() -> Unit)? = { + +Intents.nonPrivileged + + getKoin().get().extensions.values.forEach { extension -> + extension.intents.forEach { + +it + } + } + } /** @suppress Builder that shouldn't be set directly by the user. **/ public val membersBuilder: MembersBuilder = MembersBuilder() @@ -196,16 +205,29 @@ public open class ExtensibleBotBuilder { * * @param addDefaultIntents Whether to automatically add all non-privileged intents to the builder before running * the given lambda. + * @param addDefaultIntents Whether to automatically add the required intents defined within each loaded extension * * @see Intents.IntentsBuilder */ @BotBuilderDSL - public fun intents(addDefaultIntents: Boolean = true, builder: Intents.IntentsBuilder.() -> Unit) { + public fun intents( + addDefaultIntents: Boolean = true, + addExtensionIntents: Boolean = true, + builder: Intents.IntentsBuilder.() -> Unit + ) { this.intentsBuilder = { if (addDefaultIntents) { +Intents.nonPrivileged } + if (addExtensionIntents) { + getKoin().get().extensions.values.forEach { extension -> + extension.intents.forEach { + +it + } + } + } + builder() } } @@ -300,13 +322,16 @@ public open class ExtensibleBotBuilder { loadModule { single { bot } bind ExtensibleBot::class } hooksBuilder.runCreated(bot) - bot.setup() - hooksBuilder.runSetup(bot) - hooksBuilder.runBeforeExtensionsAdded(bot) + + bot.addDefaultExtensions() extensionsBuilder.extensions.forEach { bot.addExtension(it) } + hooksBuilder.runAfterExtensionsAdded(bot) + bot.setup() + hooksBuilder.runSetup(bot) + return bot } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt index 368e7caafc..e959e03952 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandRegistry.kt @@ -35,6 +35,9 @@ public open class ChatCommandRegistry : KoinComponent { /** @suppress **/ public val botSettings: ExtensibleBotBuilder by inject() + /** Whether chat commands are enabled in the bot's settings. **/ + public open val enabled: Boolean get() = botSettings.chatCommandsBuilder.enabled + /** * Directly register a [ChatCommand] to this command registry. * 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 4742920f95..b101b94458 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 @@ -19,6 +19,7 @@ import com.kotlindiscord.kord.extensions.events.ExtensionStateEvent import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord import dev.kord.core.event.Event +import dev.kord.gateway.Intent import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -132,6 +133,9 @@ public abstract class Extension : KoinComponent { /** String representing the bundle to get translations from for command names/descriptions. **/ public open val bundle: String? = null + /** Set of intents required by this extension's event handlers and commands. **/ + public open val intents: MutableSet = mutableSetOf() + /** * Override this in your subclass and use it to register your commands and event * handlers. 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 3b2e826d17..63f310fb31 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 @@ -18,6 +18,7 @@ import com.kotlindiscord.kord.extensions.commands.application.user.EphemeralUser import com.kotlindiscord.kord.extensions.commands.application.user.PublicUserCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatCommand import com.kotlindiscord.kord.extensions.commands.chat.ChatGroupCommand +import dev.kord.gateway.Intent import mu.KotlinLogging private val logger = KotlinLogging.logger {} @@ -456,6 +457,10 @@ public fun Extension.chatCommand( logger.error(e) { "Failed to register command - $e" } } + if (chatCommandRegistry.enabled) { // Don't add the intent if it won't be used + intents += Intent.GuildMessages + } + return commandObj } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt index a1dc58c5fe..287017555a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt @@ -3,7 +3,9 @@ package com.kotlindiscord.kord.extensions.extensions import com.kotlindiscord.kord.extensions.EventHandlerRegistrationException import com.kotlindiscord.kord.extensions.InvalidEventHandlerException import com.kotlindiscord.kord.extensions.events.EventHandler +import dev.kord.core.enableEvent import dev.kord.core.event.Event +import dev.kord.gateway.Intents import mu.KotlinLogging /** @@ -32,5 +34,13 @@ public suspend inline fun Extension.event( logger.error(e) { "Failed to register event handler - $e" } } + val fakeBuilder = Intents.IntentsBuilder() + + fakeBuilder.apply { + fakeBuilder.enableEvent() + } + + intents += fakeBuilder.flags().values + return eventHandler } From 81ce95d87db30db4d3d02d19ce85d48abd73e425 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 12:34:39 +0100 Subject: [PATCH 088/131] [#80] Remove unnecessary closure --- .../com/kotlindiscord/kord/extensions/extensions/_Events.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt index 287017555a..82d2edd9fb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt @@ -36,10 +36,7 @@ public suspend inline fun Extension.event( val fakeBuilder = Intents.IntentsBuilder() - fakeBuilder.apply { - fakeBuilder.enableEvent() - } - + fakeBuilder.enableEvent() intents += fakeBuilder.flags().values return eventHandler From ff00429d6b2f6a4dbf34cd8f34e176e2841ab23c Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 13:36:48 +0100 Subject: [PATCH 089/131] Limit choice converters to slash commands --- .../kord/extensions/commands/chat/ChatCommandParser.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt index 3c6debd44e..da5ebb3cf7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandParser.kt @@ -13,6 +13,7 @@ import com.kotlindiscord.kord.extensions.ExtensibleBot import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceConverter import com.kotlindiscord.kord.extensions.commands.converters.* import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import dev.kord.common.annotation.KordPreview @@ -105,6 +106,8 @@ public open class ChatCommandParser : KoinComponent { } when (val converter = currentArg.converter) { + is ChoiceConverter<*> -> error("Choice converters may only be used with slash commands") + is SingleConverter<*> -> try { val parsed = if (hasKwargs) { if (kwValue!!.size != 1) { From 37e352b4079d4d3c8bdeca4b62e8b8bf9524934b Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 18:58:31 +0100 Subject: [PATCH 090/131] Re-order some stuff to fix an error --- .../com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 2 ++ .../kord/extensions/builders/ExtensibleBotBuilder.kt | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index e3ed8298f6..052b3c78a4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -95,6 +95,8 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva settings.cacheBuilder.dataCacheBuilder.invoke(kord, kord.cache) + addDefaultExtensions() + kord.on { this.launch { send(this@on) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d16f1a3ef7..649856938a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -322,16 +322,13 @@ public open class ExtensibleBotBuilder { loadModule { single { bot } bind ExtensibleBot::class } hooksBuilder.runCreated(bot) - hooksBuilder.runBeforeExtensionsAdded(bot) + bot.setup() + hooksBuilder.runSetup(bot) - bot.addDefaultExtensions() + hooksBuilder.runBeforeExtensionsAdded(bot) extensionsBuilder.extensions.forEach { bot.addExtension(it) } - hooksBuilder.runAfterExtensionsAdded(bot) - bot.setup() - hooksBuilder.runSetup(bot) - return bot } From 2117251381e2d1cda41ac9820aa56359d1c8d2f0 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 19:44:36 +0100 Subject: [PATCH 091/131] Late event registration to fix Koin issues --- .../kord/extensions/ExtensibleBot.kt | 38 ++++++++++++++++--- .../builders/ExtensibleBotBuilder.kt | 9 +++-- .../kord/extensions/events/EventHandler.kt | 7 +++- .../kord/extensions/extensions/_Events.kt | 6 ++- .../kord/extensions/utils/_Koin.kt | 3 +- .../kord/extensions/test/bot/TestExtension.kt | 10 +++++ 6 files changed, 61 insertions(+), 12 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 052b3c78a4..e0c06c85a9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -95,8 +95,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva settings.cacheBuilder.dataCacheBuilder.invoke(kord, kord.cache) - addDefaultExtensions() - kord.on { this.launch { send(this@on) @@ -172,6 +170,16 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva "`applicationCommands` builder if you want to use them." } } + + if (!initialized) { + eventHandlers.forEach { handler -> + handler.listenerRegistrationCallable?.invoke() ?: logger.error { + "Event handler $handler does not have a listener registration callback. This should never happen!" + } + } + + initialized = true + } } /** This function adds all of the default extensions when the bot is being set up. **/ @@ -314,18 +322,38 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva * @throws EventHandlerRegistrationException Thrown if the event handler could not be registered. */ @Throws(EventHandlerRegistrationException::class) - public inline fun addEventHandler(handler: EventHandler): Job { + public inline fun addEventHandler(handler: EventHandler) { if (eventHandlers.contains(handler)) { throw EventHandlerRegistrationException( "Event handler already registered in '${handler.extension.name}' extension." ) } - val job = on { handler.call(this) } + if (initialized) { + handler.listenerRegistrationCallable?.invoke() ?: error( + "Event handler $handler does not have a listener registration callback. This should never happen!" + ) + } eventHandlers.add(handler) + } - return job + /** + * Directly register an [EventHandler] to this bot. + * + * Generally speaking, you shouldn't call this directly - instead, create an [Extension] and + * call the [Extension.event] function in your [Extension.setup] function. + * + * This function will throw an [EventHandlerRegistrationException] if the event handler has already been registered. + * + * @param handler The event handler to be registered. + * @throws EventHandlerRegistrationException Thrown if the event handler could not be registered. + */ + @Throws(EventHandlerRegistrationException::class) + public inline fun registerListenerForHandler(handler: EventHandler): Job { + return on { + handler.call(this) + } } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 649856938a..d16f1a3ef7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -322,13 +322,16 @@ public open class ExtensibleBotBuilder { loadModule { single { bot } bind ExtensibleBot::class } hooksBuilder.runCreated(bot) - bot.setup() - hooksBuilder.runSetup(bot) - hooksBuilder.runBeforeExtensionsAdded(bot) + + bot.addDefaultExtensions() extensionsBuilder.extensions.forEach { bot.addExtension(it) } + hooksBuilder.runAfterExtensionsAdded(bot) + bot.setup() + hooksBuilder.runSetup(bot) + return bot } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt index 5791554b5c..a96b103e85 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/EventHandler.kt @@ -42,8 +42,8 @@ public open class EventHandler( /** Sentry adapter, for easy access to Sentry functions. **/ public val sentry: SentryAdapter by inject() - /** Kord instance, backing the ExtensibleBot. **/ - public val kord: Kord by inject() + /** Current Kord instance powering the bot. **/ + public open val kord: Kord by inject() /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() @@ -63,6 +63,9 @@ public open class EventHandler( */ public var job: Job? = null + /** @suppress Internal hack to work around logic ordering with inline functions. **/ + public var listenerRegistrationCallable: (() -> Unit)? = null + /** Cached locale variable, stored and retrieved by [getLocale]. **/ public var resolvedLocale: Locale? = null diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt index 82d2edd9fb..e7fb2eeb01 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/extensions/_Events.kt @@ -25,8 +25,12 @@ public suspend inline fun Extension.event( try { eventHandler.validate() - eventHandler.job = bot.addEventHandler(eventHandler) + eventHandler.listenerRegistrationCallable = { + eventHandler.job = bot.registerListenerForHandler(eventHandler) + } + + bot.addEventHandler(eventHandler) eventHandlers.add(eventHandler) } catch (e: EventHandlerRegistrationException) { logger.error(e) { "Failed to register event handler - $e" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Koin.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Koin.kt index d1fdbef17c..c593a8b24a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Koin.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Koin.kt @@ -21,4 +21,5 @@ public fun loadModule( } /** Retrieve the current [Koin] instance. **/ -public fun getKoin(): Koin = KoinPlatformTools.defaultContext().get() +public fun getKoin(): Koin = + KoinPlatformTools.defaultContext().get() diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 9238504add..0e95f90d7b 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -22,13 +22,17 @@ import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.reply +import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.rest.builder.message.create.embed +import mu.KotlinLogging // They're IDs @Suppress("UnderscoresInNumericLiterals") class TestExtension : Extension() { override val name = "test" + val logger = KotlinLogging.logger {} + class ColorArgs : Arguments() { val color by colour("color", "Color to use for the embed") } @@ -74,6 +78,12 @@ class TestExtension : Extension() { } override suspend fun setup() { + event { + action { + logger.info { "Guild created: ${event.guild.name} (${event.guild.id.asString})" } + } + } + publicMessageCommand { name = "Raw Info" From 11c1a456ec4c5c7c4320ba97a82e25f9d3ab86e0 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 19:55:49 +0100 Subject: [PATCH 092/131] ADd direct message intent for chat commands --- .../com/kotlindiscord/kord/extensions/extensions/_Commands.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 63f310fb31..d8d8ae75ac 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 @@ -457,7 +457,8 @@ public fun Extension.chatCommand( logger.error(e) { "Failed to register command - $e" } } - if (chatCommandRegistry.enabled) { // Don't add the intent if it won't be used + if (chatCommandRegistry.enabled) { // Don't add the intents if they won't be used + intents += Intent.DirectMessages intents += Intent.GuildMessages } From 3dc72872c2ad12117b6772aa29398326441bcf52 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 20:15:53 +0100 Subject: [PATCH 093/131] Add more Sentry breadcrumbs to mappings extension --- .../extra/mappings/MappingsExtension.kt | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index b2b2e11355..b3b6cde106 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -20,6 +20,7 @@ import com.kotlindiscord.kord.extensions.pagination.EXPAND_EMOJI import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.core.behavior.channel.withTyping import dev.kord.core.event.message.MessageCreateEvent @@ -1048,6 +1049,15 @@ class MappingsExtension : Extension() { version: MappingsContainer?, channel: String? = null ) { + sentry.breadcrumb(BreadcrumbType.Query) { + message = "Beginning class lookup" + + data["channel"] = channel + data["namespace"] = namespace.id + data["query"] = givenQuery + data["version"] = version?.version + } + val context = newSingleThreadContext("c: $givenQuery") try { @@ -1070,9 +1080,21 @@ class MappingsExtension : Extension() { } ) + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Provider resolved, with injected default version" + + data["version"] = provider.version + } + val query = givenQuery.replace(".", "/") var pages: List> + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Attempting to run sanitized query" + + data["query"] = query + } + message.channel.withTyping { @Suppress("TooGenericExceptionCaught") val result = try { @@ -1087,6 +1109,12 @@ class MappingsExtension : Extension() { return@withContext } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Generating pages for results" + + data["resultCount"] = result.value.size + } + pages = classesToPages(namespace, result) } @@ -1142,6 +1170,10 @@ class MappingsExtension : Extension() { } } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Creating and sending paginator to Discord" + } + val paginator = MessageButtonPaginator( targetMessage = event.message, pages = pagesObj, @@ -1164,6 +1196,15 @@ class MappingsExtension : Extension() { version: MappingsContainer?, channel: String? = null ) { + sentry.breadcrumb(BreadcrumbType.Query) { + message = "Beginning field lookup" + + data["channel"] = channel + data["namespace"] = namespace.id + data["query"] = givenQuery + data["version"] = version?.version + } + val context = newSingleThreadContext("f: $givenQuery") try { @@ -1186,9 +1227,21 @@ class MappingsExtension : Extension() { } ) + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Provider resolved, with injected default version" + + data["version"] = provider.version + } + val query = givenQuery.replace(".", "/") var pages: List> + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Attempting to run sanitized query" + + data["query"] = query + } + message.channel.withTyping { @Suppress("TooGenericExceptionCaught") val result = try { @@ -1203,6 +1256,12 @@ class MappingsExtension : Extension() { return@withContext } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Generating pages for results" + + data["resultCount"] = result.value.size + } + pages = fieldsToPages(namespace, provider.get(), result) } @@ -1258,6 +1317,10 @@ class MappingsExtension : Extension() { } } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Creating and sending paginator to Discord" + } + val paginator = MessageButtonPaginator( targetMessage = event.message, pages = pagesObj, @@ -1280,6 +1343,15 @@ class MappingsExtension : Extension() { version: MappingsContainer?, channel: String? = null ) { + sentry.breadcrumb(BreadcrumbType.Query) { + message = "Beginning method lookup" + + data["channel"] = channel + data["namespace"] = namespace.id + data["query"] = givenQuery + data["version"] = version?.version + } + val context = newSingleThreadContext("m: $givenQuery") try { @@ -1302,9 +1374,21 @@ class MappingsExtension : Extension() { } ) + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Provider resolved, with injected default version" + + data["version"] = provider.version + } + val query = givenQuery.replace(".", "/") var pages: List> + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Attempting to run sanitized query" + + data["query"] = query + } + message.channel.withTyping { @Suppress("TooGenericExceptionCaught") val result = try { @@ -1319,6 +1403,12 @@ class MappingsExtension : Extension() { return@withContext } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Generating pages for results" + + data["resultCount"] = result.value.size + } + pages = methodsToPages(namespace, provider.get(), result) } @@ -1374,6 +1464,10 @@ class MappingsExtension : Extension() { } } + sentry.breadcrumb(BreadcrumbType.Info) { + message = "Creating and sending paginator to Discord" + } + val paginator = MessageButtonPaginator( targetMessage = event.message, pages = pagesObj, From 9aa89b1eaf5c1bc345bfe3f9204b718774587b23 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 22 Sep 2021 20:34:51 +0100 Subject: [PATCH 094/131] Mappings: Can't add null values to breadcrumb data --- .../extra/mappings/MappingsExtension.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt index b3b6cde106..b59483b403 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/MappingsExtension.kt @@ -1052,10 +1052,10 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Query) { message = "Beginning class lookup" - data["channel"] = channel + data["channel"] = channel ?: "N/A" data["namespace"] = namespace.id data["query"] = givenQuery - data["version"] = version?.version + data["version"] = version?.version ?: "N/A" } val context = newSingleThreadContext("c: $givenQuery") @@ -1083,7 +1083,7 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Info) { message = "Provider resolved, with injected default version" - data["version"] = provider.version + data["version"] = provider.version ?: "Unknown" } val query = givenQuery.replace(".", "/") @@ -1199,10 +1199,10 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Query) { message = "Beginning field lookup" - data["channel"] = channel + data["channel"] = channel ?: "N/A" data["namespace"] = namespace.id data["query"] = givenQuery - data["version"] = version?.version + data["version"] = version?.version ?: "N/A" } val context = newSingleThreadContext("f: $givenQuery") @@ -1230,7 +1230,7 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Info) { message = "Provider resolved, with injected default version" - data["version"] = provider.version + data["version"] = provider.version ?: "Unknown" } val query = givenQuery.replace(".", "/") @@ -1346,10 +1346,10 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Query) { message = "Beginning method lookup" - data["channel"] = channel + data["channel"] = channel ?: "N/A" data["namespace"] = namespace.id data["query"] = givenQuery - data["version"] = version?.version + data["version"] = version?.version ?: "N/A" } val context = newSingleThreadContext("m: $givenQuery") @@ -1377,7 +1377,7 @@ class MappingsExtension : Extension() { sentry.breadcrumb(BreadcrumbType.Info) { message = "Provider resolved, with injected default version" - data["version"] = provider.version + data["version"] = provider.version ?: "Unknown" } val query = givenQuery.replace(".", "/") From 0e1a671c1b03f0ae8adfc960667589a9a7d1af83 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 23 Sep 2021 11:56:13 +0100 Subject: [PATCH 095/131] [#87] Only lower slash command names --- .../commands/application/ApplicationCommand.kt | 2 +- .../application/ApplicationCommandRegistry.kt | 14 ++++++++++---- .../commands/application/slash/SlashCommand.kt | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index bd6cb5c1de..65d1482024 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -97,7 +97,7 @@ public abstract class ApplicationCommand( this.name, this.extension.bundle, locale - ).lowercase() + ) } return nameTranslationCache[locale]!! 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 012c4680fc..dbd7aa9433 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 @@ -543,7 +543,9 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = messageCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteGeneric(command, id) } + if (delete) { + deleteGeneric(command, id) + } return messageCommands.remove(id) } @@ -553,7 +555,9 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = slashCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteGeneric(command, id) } + if (delete) { + deleteGeneric(command, id) + } return slashCommands.remove(id) } @@ -563,7 +567,9 @@ public open class ApplicationCommandRegistry : KoinComponent { val filtered = userCommands.filter { it.value == command } val id = filtered.keys.firstOrNull() ?: return null - if (delete) { deleteGeneric(command, id) } + if (delete) { + deleteGeneric(command, id) + } return userCommands.remove(id) } @@ -721,7 +727,7 @@ public open class ApplicationCommandRegistry : KoinComponent { public open fun ApplicationCommand<*>.matches( locale: Locale, other: dev.kord.core.entity.application.ApplicationCommand - ): Boolean = getTranslatedName(locale) == other.name && type == other.type + ): Boolean = type == other.type && getTranslatedName(locale).equals(other.name, true) // endregion } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 4114501de2..100ad44e15 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -20,6 +20,7 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import mu.KLogger import mu.KotlinLogging +import java.util.* /** * Slash command, executed directly in the chat input. @@ -91,6 +92,20 @@ public abstract class SlashCommand, A : Arguments> } } + public override fun getTranslatedName(locale: Locale): String { + // Only slash commands need this to be lower-cased. + + if (!nameTranslationCache.containsKey(locale)) { + nameTranslationCache[locale] = translationsProvider.translate( + this.name, + this.extension.bundle, + locale + ).lowercase() + } + + return nameTranslationCache[locale]!! + } + /** Call this to supply a command [body], to be called when the command is executed. **/ public fun action(action: suspend C.() -> Unit) { body = action From e4c6a6d8a68baca8f45aa7679b4bdc47278a04e6 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 23 Sep 2021 16:27:29 +0100 Subject: [PATCH 096/131] Yeet a couple cache hits in check utils --- .../kord/extensions/checks/CheckUtils.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 37ede67340..035649ed8c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -212,9 +212,29 @@ public suspend fun guildFor(event: Event): GuildBehavior? { is MemberLeaveEvent -> event.guild is MemberUpdateEvent -> event.guild is MessageBulkDeleteEvent -> event.guild - is MessageCreateEvent -> event.message.getGuildOrNull() + + is MessageCreateEvent -> { + val guildId = event.message.data.guildId.value + + if (guildId == null) { + guildId + } else { + event.kord.unsafe.guild(guildId) + } + } + is MessageDeleteEvent -> event.guild - is MessageUpdateEvent -> event.getMessage().getGuildOrNull() + + is MessageUpdateEvent -> { + val guildId = event.new.guildId.value + + if (guildId == null) { + guildId + } else { + event.kord.unsafe.guild(guildId) + } + } + is NewsChannelCreateEvent -> event.channel.guild is NewsChannelDeleteEvent -> event.channel.guild is NewsChannelUpdateEvent -> event.channel.guild @@ -398,6 +418,7 @@ public suspend fun userFor(event: Event): UserBehavior? { is DMChannelCreateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelDeleteEvent -> event.channel.recipients.first { it.id != event.kord.selfId } is DMChannelUpdateEvent -> event.channel.recipients.first { it.id != event.kord.selfId } + is InteractionCreateEvent -> event.interaction.user is MemberJoinEvent -> event.member is MemberLeaveEvent -> event.user From 067e0b385ee8268aeb05d48196d5a5094c681411 Mon Sep 17 00:00:00 2001 From: Alex <2230292+ByteAlex@users.noreply.github.com> Date: Fri, 24 Sep 2021 12:33:36 +0200 Subject: [PATCH 097/131] Improve contexts (#88) --- .../kord/extensions/checks/CheckUtils.kt | 39 +++++++++++-------- .../application/ApplicationCommandContext.kt | 9 ++++- .../commands/chat/ChatCommandContext.kt | 7 +++- .../extensions/components/ComponentContext.kt | 10 +++-- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 035649ed8c..4be0a30881 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -1,13 +1,18 @@ -@file:OptIn(KordPreview::class) +@file:OptIn(KordPreview::class, KordUnsafe::class, KordExperimental::class) package com.kotlindiscord.kord.extensions.checks import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.utils.authorId +import dev.kord.common.annotation.KordExperimental import dev.kord.common.annotation.KordPreview +import dev.kord.common.annotation.KordUnsafe import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.* import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.threads.ThreadChannelBehavior +import dev.kord.core.cache.data.toData +import dev.kord.core.entity.Member import dev.kord.core.entity.interaction.GuildApplicationCommandInteraction import dev.kord.core.event.Event import dev.kord.core.event.channel.* @@ -201,7 +206,7 @@ public suspend fun guildFor(event: Event): GuildBehavior? { if (guildId == null) { null } else { - event.kord.getGuild(guildId) + event.kord.unsafe.guild(guildId) } } @@ -281,21 +286,21 @@ public suspend fun memberFor(event: Event): MemberBehavior? { event is MemberJoinEvent -> event.member event is MemberUpdateEvent -> event.member - - event is MessageCreateEvent && event.message.getGuildOrNull() != null -> - event.message.getAuthorAsMember() - - event is MessageDeleteEvent && event.message?.getGuildOrNull() != null -> - event.message?.getAuthorAsMember() - - event is MessageUpdateEvent && event.message.asMessageOrNull()?.getGuildOrNull() != null -> - event.getMessage().getAuthorAsMember() - - event is ReactionAddEvent && event.message.asMessageOrNull()?.getGuildOrNull() != null -> - event.getUserAsMember() - - event is ReactionRemoveEvent && event.message.asMessageOrNull()?.getGuildOrNull() != null -> - event.getUserAsMember() + event is MessageCreateEvent -> event.member + event is MessageDeleteEvent -> event.message?.data?.guildId?.value + ?.let { event.kord.unsafe.member(it, event.message!!.data.authorId) } + + event is MessageUpdateEvent -> { + val message = event.new + if (message.author.value != null && message.member.value != null) { + val userData = message.author.value!!.toData() + val memberData = message.member.value!!.toData(userData.id, event.new.guildId.value!!) + return Member(memberData, userData, event.kord) + } + return null + } + event is ReactionAddEvent -> event.userAsMember + event is ReactionRemoveEvent -> event.userAsMember event is TypingStartEvent -> if (event.guildId != null) { event.getGuild()!!.getMemberOrNull(event.userId) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt index 1626c00d59..b023c43abe 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommandContext.kt @@ -1,11 +1,16 @@ +@file:OptIn(KordUnsafe::class, KordExperimental::class) + package com.kotlindiscord.kord.extensions.commands.application import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.commands.CommandContext +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.interaction.GuildApplicationCommandInteraction import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent import org.koin.core.component.inject @@ -50,11 +55,11 @@ public abstract class ApplicationCommandContext( /** Extract guild information from event data, if that context is available. **/ public override suspend fun getGuild(): GuildBehavior? = - genericEvent.interaction.data.guildId.value ?.let { GuildBehavior(it, genericEvent.kord) } + genericEvent.interaction.data.guildId.value?.let { genericEvent.kord.unsafe.guild(it) } /** Extract member information from event data, if that context is available. **/ public override suspend fun getMember(): MemberBehavior? = - getGuild()?.let { MemberBehavior(it.id, genericEvent.interaction.user.id, genericEvent.kord) } + (genericEvent.interaction as? GuildApplicationCommandInteraction)?.member /** Extract user information from event data, if that context is available. **/ public override suspend fun getUser(): UserBehavior = diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt index 279054c9cf..f3f43c96da 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommandContext.kt @@ -1,4 +1,4 @@ -@file:OptIn(KordPreview::class) +@file:OptIn(KordPreview::class, KordUnsafe::class, KordExperimental::class) package com.kotlindiscord.kord.extensions.commands.chat @@ -10,7 +10,9 @@ import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.utils.respond +import dev.kord.common.annotation.KordExperimental import dev.kord.common.annotation.KordPreview +import dev.kord.common.annotation.KordUnsafe import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior @@ -69,7 +71,8 @@ public open class ChatCommandContext( } override suspend fun getChannel(): MessageChannelBehavior = event.message.channel - override suspend fun getGuild(): GuildBehavior? = event.guildId?.let { GuildBehavior(it, event.kord) } + override suspend fun getGuild(): GuildBehavior? = event.guildId + ?.let { event.kord.unsafe.guild(it) } override suspend fun getMember(): MemberBehavior? = event.member override suspend fun getUser(): UserBehavior? = event.message.author diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt index a9e42b58ae..fbbde1e2b7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContext.kt @@ -1,3 +1,5 @@ +@file:OptIn(KordUnsafe::class, KordExperimental::class) + package com.kotlindiscord.kord.extensions.components import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder @@ -6,11 +8,13 @@ import com.kotlindiscord.kord.extensions.checks.guildFor import com.kotlindiscord.kord.extensions.checks.userFor import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.sentry.SentryContext +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.MemberBehavior import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior +import dev.kord.core.entity.Member import dev.kord.core.entity.Message import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import org.koin.core.component.KoinComponent @@ -72,12 +76,12 @@ public abstract class ComponentContext( /** Extract guild information from event data, if that context is available. **/ @JvmName("getGuild1") public fun getGuild(): GuildBehavior? = - (event.interaction.channel as? GuildChannelBehavior)?.guild + event.interaction.data.guildId.value?.let { event.kord.unsafe.guild(it) } /** Extract member information from event data, if that context is available. **/ @JvmName("getMember1") public suspend fun getMember(): MemberBehavior? = - this.guild?.getMember(event.interaction.user.id) + getGuild()?.let { Member(event.interaction.data.member.value!!, event.interaction.user.data, event.kord) } /** Extract message information from event data, if that context is available. **/ @JvmName("getMessage1") From 528148a37b214ac510f2df3fa2a2668311309430 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Fri, 24 Sep 2021 11:42:49 +0100 Subject: [PATCH 098/131] Small cache fixes and converter rename --- .../commands/application/message/MessageCommand.kt | 4 ++++ .../extensions/commands/application/user/UserCommand.kt | 4 ++++ .../kord/extensions/commands/chat/ChatCommand.kt | 7 ++++++- .../{SupportedLocale.kt => SupportedLocaleConverter.kt} | 2 +- .../kord/extensions/components/ComponentWithAction.kt | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/{SupportedLocale.kt => SupportedLocaleConverter.kt} (98%) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 3c45699983..0280ecde4f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -52,6 +52,10 @@ public abstract class MessageCommand>( /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { + if (requiredPerms.isEmpty()) { + return // Nothing to check, don't try to hit the cache + } + if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) .permissionsForMember(kord.selfId) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index f6d19564b3..0ebb2a31d3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -52,6 +52,10 @@ public abstract class UserCommand>( /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: C) { + if (requiredPerms.isEmpty()) { + return // Nothing to check, don't try to hit the cache + } + if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) .permissionsForMember(kord.selfId) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 7e086319cb..9eebd0d256 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -328,6 +328,10 @@ public open class ChatCommand( /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) public open suspend fun checkBotPerms(context: ChatCommandContext) { + if (requiredPerms.isEmpty()) { + return // Nothing to check, don't try to hit the cache + } + if (context.guild != null) { val perms = (context.channel.asChannel() as GuildChannel) .permissionsForMember(kord.selfId) @@ -339,8 +343,9 @@ public open class ChatCommand( context.translate( "commands.error.missingBotPermissions", null, + replacements = arrayOf( - missingPerms.map { it.translate(context) }.joinToString(", ") + missingPerms.map { it.translate(context.getLocale()) }.joinToString(", ") ) ) ) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt similarity index 98% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt index 1dc3f967dc..e06eb39b3f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocale.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt @@ -37,7 +37,7 @@ import java.util.* types = [ConverterType.DEFAULTING, ConverterType.LIST, ConverterType.OPTIONAL, ConverterType.SINGLE], ) @OptIn(KordPreview::class) -public class SupportedLocale( +public class SupportedLocaleConverter( override var validator: Validator = null ) : SingleConverter() { override val signatureTypeString: String = "converters.supportedLocale.signatureType" diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index 1ede9928f7..cfbf026a87 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -109,6 +109,10 @@ public abstract class ComponentWithAction Date: Sat, 25 Sep 2021 15:54:11 +0100 Subject: [PATCH 099/131] Add delete utils for public followup messages --- .../kord/extensions/utils/_Message.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt index a3c943a0c6..354acf7e6c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt @@ -9,6 +9,7 @@ import dev.kord.core.behavior.MessageBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.interaction.PublicFollowupMessageBehavior import dev.kord.core.behavior.reply import dev.kord.core.cache.data.MessageData import dev.kord.core.entity.* @@ -42,6 +43,19 @@ public suspend fun MessageBehavior.deleteIgnoringNotFound() { } } +/** + * Deletes a public follow-up, catching and ignoring a HTTP 404 (Not Found) exception. + */ +public suspend fun PublicFollowupMessageBehavior.deleteIgnoringNotFound() { + try { + delete() + } catch (e: RestRequestException) { + if (e.hasNotStatus(HttpStatusCode.NotFound)) { + throw e + } + } +} + /** * Deletes a message after a delay. * @@ -69,6 +83,33 @@ public fun MessageBehavior.delete(millis: Long, retry: Boolean = true): Job { } } +/** + * Deletes a public follow-up after a delay. + * + * This function **does not block**. + * + * @param millis The delay before deleting the message, in milliseconds. + * @return Job spawned by the CoroutineScope. + */ +public fun PublicFollowupMessageBehavior.delete(millis: Long, retry: Boolean = true): Job { + return kord.launch { + delay(millis) + + try { + this@delete.deleteIgnoringNotFound() + } catch (e: RestRequestException) { + val message = this@delete + + if (retry) { + logger.debug(e) { "Failed to delete message, retrying: $message" } + this@delete.delete(millis, false) + } else { + logger.error(e) { "Failed to delete message: $message" } + } + } + } +} + /** * Add a reaction to this message, using the Unicode emoji represented by the given string. * From dba13af7b82168db8f45b660f48f9ba416bcebb5 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 27 Sep 2021 10:36:16 +0100 Subject: [PATCH 100/131] [#92] Add locking options for commands and components --- .../kord/extensions/commands/Command.kt | 13 ++++++- .../application/ApplicationCommand.kt | 4 +- .../message/EphemeralMessageCommand.kt | 2 +- .../message/EphemeralMessageCommandContext.kt | 2 +- .../message/PublicMessageCommand.kt | 2 +- .../message/PublicMessageCommandContext.kt | 2 +- .../slash/EphemeralSlashCommand.kt | 2 +- .../slash/EphemeralSlashCommandContext.kt | 2 +- .../application/slash/PublicSlashCommand.kt | 2 +- .../slash/PublicSlashCommandContext.kt | 2 +- .../application/user/EphemeralUserCommand.kt | 2 +- .../user/EphemeralUserCommandContext.kt | 2 +- .../application/user/PublicUserCommand.kt | 2 +- .../user/PublicUserCommandContext.kt | 2 +- .../extensions/commands/chat/ChatCommand.kt | 12 +++--- .../components/ComponentWithAction.kt | 12 +++++- .../buttons/EphemeralInteractionButton.kt | 8 ++-- .../EphemeralInteractionButtonContext.kt | 2 +- .../buttons/PublicInteractionButton.kt | 8 ++-- .../buttons/PublicInteractionButtonContext.kt | 2 +- .../components/menus/EphemeralSelectMenu.kt | 8 ++-- .../menus/EphemeralSelectMenuContext.kt | 2 +- .../components/menus/PublicSelectMenu.kt | 8 ++-- .../menus/PublicSelectMenuContext.kt | 2 +- .../extensions/impl/SentryExtension.kt | 2 +- .../EphemeralInteractionContext.kt | 2 +- .../kord/extensions/types/Lockable.kt | 37 +++++++++++++++++++ .../PublicInteractionContext.kt | 2 +- .../kord/extensions/test/bot/TestExtension.kt | 4 +- 29 files changed, 105 insertions(+), 47 deletions(-) rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/{interactions => types}/EphemeralInteractionContext.kt (97%) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/Lockable.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/{interactions => types}/PublicInteractionContext.kt (97%) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt index 97a48a3e3d..3de1601ab8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/Command.kt @@ -6,8 +6,10 @@ import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.events.CommandEvent import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.Lockable import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex /** * Abstract base class representing the few things that command objects can have in common. @@ -17,12 +19,17 @@ import kotlinx.coroutines.launch * @property extension The extension object this command belongs to. */ @ExtensionDSL -public abstract class Command(public val extension: Extension) { +public abstract class Command(public val extension: Extension) : Lockable { /** * The name of this command, for invocation and help commands. */ public open lateinit var name: String + /** Set this to `true` to lock command execution with a Mutex. **/ + public override var locking: Boolean = false + + override var mutex: Mutex? = null + /** * An internal function used to ensure that all of a command's required arguments are present and correct. * @@ -33,6 +40,10 @@ public abstract class Command(public val extension: Extension) { if (!::name.isInitialized || name.isEmpty()) { throw InvalidCommandException(null, "No command name given.") } + + if (locking && mutex == null) { + mutex = Mutex() + } } /** Quick shortcut for emitting a command event without blocking. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index 65d1482024..b7963eab06 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -186,9 +186,9 @@ public abstract class ApplicationCommand( } /** Called in order to execute the command. **/ - public open suspend fun doCall(event: E) { + public open suspend fun doCall(event: E): Unit = withLock { if (!runChecks(event)) { - return + return@withLock } call(event) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index 9270a886c8..e3a2745b5b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt index cbd0414e57..b59fc7d4c7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 8a220c5aac..8fb3104394 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandFai import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt index 6e81775764..5d20eb180e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.message -import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index ad4580284f..86622a874c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -7,7 +7,7 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt index c180f2d0a4..bd778257dd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommandContext.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 7140622ade..702a1f0022 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -7,7 +7,7 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.interaction.GroupCommand diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt index 43fd3766a8..a698ccb2eb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommandContext.kt @@ -1,7 +1,7 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.commands.Arguments -import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index eca67205ae..a67822470c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFai import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt index 28525e2104..f9dd4e51ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.interactions.EphemeralInteractionContext +import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index e898b7df38..60429af122 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -8,7 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailed import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension -import com.kotlindiscord.kord.extensions.interactions.respond +import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt index 04d41d6250..4368449d64 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommandContext.kt @@ -1,6 +1,6 @@ package com.kotlindiscord.kord.extensions.commands.application.user -import com.kotlindiscord.kord.extensions.interactions.PublicInteractionContext +import com.kotlindiscord.kord.extensions.types.PublicInteractionContext import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 9eebd0d256..cacfb359d0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -375,7 +375,7 @@ public open class ChatCommand( parser: StringParser, argString: String, skipChecks: Boolean = false - ) { + ): Unit = withLock { emitEventAsync(ChatCommandInvocationEvent(this, event)) try { @@ -388,13 +388,13 @@ public open class ChatCommand( ) ) - return + return@withLock } } catch (e: DiscordRelayedException) { emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) event.message.respond(e.reason) - return + return@withLock } val context = ChatCommandContext(this, event, commandName, parser, argString) @@ -433,7 +433,7 @@ public open class ChatCommand( event.message.respond(e.reason) emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) - return + return@withLock } if (this.arguments != null) { @@ -444,7 +444,7 @@ public open class ChatCommand( event.message.respond(e.reason) emitEventAsync(ChatCommandFailedParsingEvent(this, event, e)) - return + return@withLock } } @@ -518,7 +518,7 @@ public open class ChatCommand( ) } - return + return@withLock } emitEventAsync(ChatCommandSucceededEvent(this, event)) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index cfbf026a87..9c52fd3291 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.types.Lockable import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.scheduling.Task @@ -10,6 +11,7 @@ import com.kotlindiscord.kord.extensions.utils.translate import dev.kord.common.entity.Permission import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.event.interaction.ComponentInteractionCreateEvent +import kotlinx.coroutines.sync.Mutex import mu.KLogger import mu.KotlinLogging @@ -24,7 +26,7 @@ import mu.KotlinLogging */ public abstract class ComponentWithAction>( public open val timeoutTask: Task? -) : ComponentWithID() { +) : ComponentWithID(), Lockable { private val logger: KLogger = KotlinLogging.logger {} /** Whether to use a deferred ack, which will prevent Discord's "Thinking..." message. **/ @@ -36,6 +38,10 @@ public abstract class ComponentWithAction = mutableSetOf() + public override var locking: Boolean = false + + override var mutex: Mutex? = null + /** Component body, to be called when the component is interacted with. **/ public lateinit var body: suspend C.() -> Unit @@ -74,6 +80,10 @@ public abstract class ComponentWithAction withLock(body: suspend () -> T) { + try { + lock() + + body() + } finally { + unlock() + } + } + + /** Lock the mutex, if locking is enabled - suspending until it's unlocked. **/ + public suspend fun lock() { + if (locking) { + mutex?.lock() + } + } + + /** Unlock the mutex, if it's locked. **/ + public fun unlock() { + if (locking) { + mutex?.unlock() + } + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt similarity index 97% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt index 784384fe69..1848fd9745 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/interactions/PublicInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt @@ -1,4 +1,4 @@ -package com.kotlindiscord.kord.extensions.interactions +package com.kotlindiscord.kord.extensions.types import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 0e95f90d7b..fb78afe3bc 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -11,11 +11,11 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.* import com.kotlindiscord.kord.extensions.components.* import com.kotlindiscord.kord.extensions.components.types.emoji import com.kotlindiscord.kord.extensions.extensions.* -import com.kotlindiscord.kord.extensions.interactions.editingPaginator -import com.kotlindiscord.kord.extensions.interactions.respond import com.kotlindiscord.kord.extensions.pagination.MessageButtonPaginator import com.kotlindiscord.kord.extensions.pagination.pages.Page import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import com.kotlindiscord.kord.extensions.types.editingPaginator +import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.respond import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.ButtonStyle From 905b7a8873f749c9fb441f99f19e48368cbe407d Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 27 Sep 2021 19:03:22 +0100 Subject: [PATCH 101/131] Application command fixes --- .../application/ApplicationCommand.kt | 3 + .../application/ApplicationCommandRegistry.kt | 324 +++++++++++------- .../application/slash/SlashCommand.kt | 15 + .../commands/application/slash/SlashGroup.kt | 27 +- 4 files changed, 253 insertions(+), 116 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index b7963eab06..b89aaaa6d5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -90,6 +90,9 @@ public abstract class ApplicationCommand( /** Translation cache, so we don't have to look up translations every time. **/ public open val nameTranslationCache: MutableMap = mutableMapOf() + /** Translation cache, so we don't have to look up translations every time. **/ + public open val descriptionTranslationCache: MutableMap = mutableMapOf() + /** Return this command's name translated for the given locale, cached as required. **/ public open fun getTranslatedName(locale: Locale): String { if (!nameTranslationCache.containsKey(locale)) { 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 dbd7aa9433..f4973105e0 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 @@ -18,6 +18,9 @@ import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.behavior.createApplicationCommands +import dev.kord.core.behavior.createChatInputCommand +import dev.kord.core.behavior.createMessageCommand +import dev.kord.core.behavior.createUserCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent @@ -280,7 +283,7 @@ public open class ApplicationCommandRegistry : KoinComponent { is UserCommand<*> -> user(name) { this.register(locale, it) } is SlashCommand<*, *> -> input( - name, translationsProvider.translate(it.description, it.extension.bundle, locale = locale) + name, it.getTranslatedDescription(locale) ) { this.register(locale, it) } } } @@ -299,7 +302,7 @@ public open class ApplicationCommandRegistry : KoinComponent { is UserCommand<*> -> user(name) { this.register(locale, it) } is SlashCommand<*, *> -> input( - name, translationsProvider.translate(it.description, it.extension.bundle, locale = locale) + name, it.getTranslatedDescription(locale) ) { this.register(locale, it) } } } @@ -332,106 +335,6 @@ public open class ApplicationCommandRegistry : KoinComponent { } } - /** Register a generic application command. **/ - public open suspend fun registerGeneric(command: ApplicationCommand<*>): ApplicationCommand<*>? { - val locale = bot.settings.i18nBuilder.defaultLocale - - val guild = if (command.guildId != null) { - kord.getGuild(command.guildId!!) - } else { - null - } - - val response = if (guild == null) { - // We're registering global commands here, if the guild is null - - kord.createGlobalApplicationCommands { - val name = command.getTranslatedName(locale) - - logger.trace { "Adding/updating global ${command.type.name} command: $name" } - - when (command) { - is MessageCommand<*> -> message(name) { this.register(locale, command) } - is UserCommand<*> -> user(name) { this.register(locale, command) } - - is SlashCommand<*, *> -> input( - name, - - translationsProvider.translate( - command.description, - command.extension.bundle, - locale = locale - ) - ) { this.register(locale, command) } - } - }.toList() - } else { - // We're registering guild-specific commands here, if the guild is available - - guild.createApplicationCommands { - val name = command.getTranslatedName(locale) - - logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } - - when (command) { - is MessageCommand<*> -> message(name) { this.register(locale, command) } - is UserCommand<*> -> user(name) { this.register(locale, command) } - - is SlashCommand<*, *> -> input( - name, - - translationsProvider.translate( - command.description, - command.extension.bundle, - locale = locale - ) - ) { this.register(locale, command) } - } - }.toList() - } - - val match = response.first { command.matches(locale, it) } - - try { - if (guild != null) { - kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, match.id) { - command.allowedUsers.map { user(it, true) } - command.disallowedUsers.map { user(it, false) } - - command.allowedRoles.map { role(it, true) } - command.disallowedRoles.map { role(it, false) } - } - - logger.trace { "Applied permissions for command: ${command.name} ($command)" } - } else { - logger.warn { "Applying permissions to global application commands is currently not supported." } - } - } catch (e: KtorRequestException) { - logger.error(e) { - "Failed to apply application command permissions. This command will not be registered." + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - } catch (t: Throwable) { - logger.error(t) { - "Failed to apply application command permissions. This command will not be registered." - } - - return null - } - - when (command) { - is MessageCommand<*> -> messageCommands[match.id] = command - is SlashCommand<*, *> -> slashCommands[match.id] = command - is UserCommand<*> -> userCommands[match.id] = command - } - - return command - } - // endregion // region: Typed batch registration functions @@ -440,7 +343,7 @@ public open class ApplicationCommandRegistry : KoinComponent { public open suspend fun registerAll(vararg commands: MessageCommand<*>): List> = commands.map { try { - registerGeneric(it) as MessageCommand<*> + register(it) as MessageCommand<*> } catch (e: KtorRequestException) { logger.warn(e) { "Failed to register ${it.type.name} command: ${it.name}" + @@ -463,7 +366,7 @@ public open class ApplicationCommandRegistry : KoinComponent { public open suspend fun registerAll(vararg commands: SlashCommand<*, *>): List> = commands.map { try { - registerGeneric(it) as SlashCommand<*, *> + register(it) as SlashCommand<*, *> } catch (e: KtorRequestException) { logger.warn(e) { "Failed to register ${it.type.name} command: ${it.name}" + @@ -486,7 +389,7 @@ public open class ApplicationCommandRegistry : KoinComponent { public open suspend fun registerAll(vararg commands: UserCommand<*>): List> = commands.map { try { - registerGeneric(it) as UserCommand<*> + register(it) as UserCommand<*> } catch (e: KtorRequestException) { logger.warn(e) { "Failed to register ${it.type.name} command: ${it.name}" + @@ -510,16 +413,203 @@ public open class ApplicationCommandRegistry : KoinComponent { // region: Typed registration functions /** Register a message command. **/ - public open suspend fun register(command: MessageCommand<*>): MessageCommand<*> = - registerGeneric(command) as MessageCommand<*> + public open suspend fun register(command: MessageCommand<*>): MessageCommand<*>? { + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (command.guildId != null) { + kord.getGuild(command.guildId!!) + } else { + null + } + + val name = command.getTranslatedName(locale) + + val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalMessageCommand(name) { + logger.trace { "Adding/updating global ${command.type.name} command: $name" } + + this.register(locale, command) + } + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createMessageCommand(name) { + logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } + + this.register(locale, command) + } + } + + try { + if (guild != null) { + kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { + command.allowedUsers.map { user(it, true) } + command.disallowedUsers.map { user(it, false) } + + command.allowedRoles.map { role(it, true) } + command.disallowedRoles.map { role(it, false) } + } + + logger.trace { "Applied permissions for command: ${command.name} ($command)" } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions. This command will not be registered." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions. This command will not be registered." + } + + return null + } + + messageCommands[response.id] = command + + return command + } /** Register a slash command. **/ - public open suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *> = - registerGeneric(command) as SlashCommand<*, *> + public open suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *>? { + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (command.guildId != null) { + kord.getGuild(command.guildId!!) + } else { + null + } + + val name = command.getTranslatedName(locale) + val description = command.getTranslatedDescription(locale) + + val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalChatInputCommand(name, description) { + logger.trace { "Adding/updating global ${command.type.name} command: $name" } + + this.register(locale, command) + } + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createChatInputCommand(name, description) { + logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } + + this.register(locale, command) + } + } + + try { + if (guild != null) { + kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { + command.allowedUsers.map { user(it, true) } + command.disallowedUsers.map { user(it, false) } + + command.allowedRoles.map { role(it, true) } + command.disallowedRoles.map { role(it, false) } + } + + logger.trace { "Applied permissions for command: ${command.name} ($command)" } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions. This command will not be registered." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions. This command will not be registered." + } + + return null + } + + slashCommands[response.id] = command + + return command + } /** Register a user command. **/ - public open suspend fun register(command: UserCommand<*>): UserCommand<*> = - registerGeneric(command) as UserCommand<*> + public open suspend fun register(command: UserCommand<*>): UserCommand<*>? { + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (command.guildId != null) { + kord.getGuild(command.guildId!!) + } else { + null + } + + val name = command.getTranslatedName(locale) + + val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalUserCommand(name) { + logger.trace { "Adding/updating global ${command.type.name} command: $name" } + + this.register(locale, command) + } + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createUserCommand(name) { + logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } + + this.register(locale, command) + } + } + + try { + if (guild != null) { + kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { + command.allowedUsers.map { user(it, true) } + command.disallowedUsers.map { user(it, false) } + + command.allowedRoles.map { role(it, true) } + command.disallowedRoles.map { role(it, false) } + } + + logger.trace { "Applied permissions for command: ${command.name} ($command)" } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions. This command will not be registered." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions. This command will not be registered." + } + + return null + } + + userCommands[response.id] = command + + return command + } // endregion @@ -581,7 +671,11 @@ public open class ApplicationCommandRegistry : KoinComponent { ) { try { if (command.guildId != null) { - kord.unsafe.guildApplicationCommand(command.guildId!!, kord.resources.applicationId, discordCommandId) + kord.unsafe.guildApplicationCommand( + command.guildId!!, + kord.resources.applicationId, + discordCommandId + ) } else { kord.unsafe.globalApplicationCommand(kord.resources.applicationId, discordCommandId).delete() } @@ -671,7 +765,7 @@ public open class ApplicationCommandRegistry : KoinComponent { this.subCommand( it.name, - translationsProvider.translate(it.description, command.extension.bundle, locale = locale) + it.getTranslatedDescription(locale) ) { if (args != null) { if (this.options == null) this.options = mutableListOf() @@ -682,7 +776,7 @@ public open class ApplicationCommandRegistry : KoinComponent { } command.groups.values.forEach { group -> - this.group(group.name, group.description) { + this.group(group.name, group.getTranslatedDescription(locale)) { group.subCommands.forEach { val args = it.arguments?.invoke()?.args?.map { arg -> val converter = arg.converter @@ -697,7 +791,7 @@ public open class ApplicationCommandRegistry : KoinComponent { this.subCommand( it.name, - translationsProvider.translate(it.description, command.extension.bundle, locale = locale) + it.getTranslatedDescription(locale) ) { if (args != null) { if (this.options == null) this.options = mutableListOf() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 100ad44e15..1c30110441 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -106,6 +106,21 @@ public abstract class SlashCommand, A : Arguments> return nameTranslationCache[locale]!! } + /** Return this command's description translated for the given locale, cached as required. **/ + public fun getTranslatedDescription(locale: Locale): String { + // Only slash commands need this to be lower-cased. + + if (!descriptionTranslationCache.containsKey(locale)) { + descriptionTranslationCache[locale] = translationsProvider.translate( + this.description, + this.extension.bundle, + locale + ).lowercase() + } + + return descriptionTranslationCache[locale]!! + } + /** Call this to supply a command [body], to be called when the command is executed. **/ public fun action(action: suspend C.() -> Unit) { body = action diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt index 324cef4404..34589d09cb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashGroup.kt @@ -1,8 +1,12 @@ package com.kotlindiscord.kord.extensions.commands.application.slash import com.kotlindiscord.kord.extensions.InvalidCommandException +import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import mu.KLogger import mu.KotlinLogging +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.util.* /** * Slash command group, containing other slash commands. @@ -13,7 +17,10 @@ import mu.KotlinLogging public class SlashGroup( public val name: String, public val parent: SlashCommand<*, *> -) { +) : KoinComponent { + /** Translations provider, for retrieving translations. **/ + public val translationsProvider: TranslationsProvider by inject() + /** @suppress **/ public val logger: KLogger = KotlinLogging.logger {} @@ -23,6 +30,24 @@ public class SlashGroup( /** Command group description, which is required and shown on Discord. **/ public lateinit var description: String + /** Translation cache, so we don't have to look up translations every time. **/ + public val descriptionTranslationCache: MutableMap = mutableMapOf() + + /** Return this group's description translated for the given locale, cached as required. **/ + public fun getTranslatedDescription(locale: Locale): String { + // Only slash commands need this to be lower-cased. + + if (!descriptionTranslationCache.containsKey(locale)) { + descriptionTranslationCache[locale] = translationsProvider.translate( + this.description, + this.parent.extension.bundle, + locale + ).lowercase() + } + + return descriptionTranslationCache[locale]!! + } + /** * Validate this command group, ensuring it has everything it needs. * From d6c1f757f6f232e519ac6f0afdd783e0793dc0d2 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 28 Sep 2021 10:49:40 +0100 Subject: [PATCH 102/131] Command check inheritance --- .../commands/application/slash/SlashCommand.kt | 10 +++++++++- .../extensions/commands/chat/ChatGroupCommand.kt | 10 ++++++++++ .../kord/extensions/commands/chat/ChatSubCommand.kt | 12 ++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 1c30110441..8907a992b5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -199,7 +199,15 @@ public abstract class SlashCommand, A : Arguments> override suspend fun runChecks(event: ChatInputCommandInteractionCreateEvent): Boolean { val locale = event.getLocale() - val result = super.runChecks(event) + var result = super.runChecks(event) + + if (result && parentCommand != null) { + result = parentCommand!!.runChecks(event) + } + + if (result && parentGroup != null) { + result = parentGroup!!.parent.runChecks(event) + } if (result) { settings.applicationCommandsBuilder.slashCommandChecks.forEach { check -> diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt index c0ef5fd0ab..911545ca0d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatGroupCommand.kt @@ -45,6 +45,16 @@ public open class ChatGroupCommand( sendHelp() } + override suspend fun runChecks(event: MessageCreateEvent, sendMessage: Boolean): Boolean { + var result = parent?.runChecks(event, sendMessage) ?: true + + if (result) { + result = super.runChecks(event, sendMessage) + } + + return result + } + /** * An internal function used to ensure that all of a command group's required arguments are present. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt index 8ac1c92ca9..5475aa8af4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatSubCommand.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.commands.chat import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.extensions.Extension +import dev.kord.core.event.message.MessageCreateEvent import java.util.* /** @@ -19,6 +20,17 @@ public open class ChatSubCommand( arguments: (() -> T)? = null, public open val parent: ChatGroupCommand ) : ChatCommand(extension, arguments) { + + override suspend fun runChecks(event: MessageCreateEvent, sendMessage: Boolean): Boolean { + var result = parent.runChecks(event, sendMessage) + + if (result) { + result = super.runChecks(event, sendMessage) + } + + return result + } + /** Get the full command name, translated, with parent commands taken into account. **/ public open suspend fun getFullTranslatedName(locale: Locale): String = parent.getFullTranslatedName(locale) + " " + this.getTranslatedName(locale) From 06923674bb1f518543ecc64677aa6598e4ee323c Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 28 Sep 2021 11:40:35 +0100 Subject: [PATCH 103/131] Don't lowercase slash command description --- .../kord/extensions/commands/application/slash/SlashCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index 8907a992b5..e8798d754f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -115,7 +115,7 @@ public abstract class SlashCommand, A : Arguments> this.description, this.extension.bundle, locale - ).lowercase() + ) } return descriptionTranslationCache[locale]!! From 6db4da679d0e0903b4418e0f217a2a358882bbdc Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Wed, 29 Sep 2021 20:03:42 +0200 Subject: [PATCH 104/131] Cleanup gradle files (#96) --- .idea/GradleUpdaterPlugin.xml | 6 + .idea/compiler.xml | 3 - annotation-processor/build.gradle.kts | 144 +------------- annotations/build.gradle.kts | 145 +------------- buildSrc/build.gradle.kts | 18 ++ buildSrc/src/main/kotlin/GitTools.kt | 25 +++ .../disable-explicit-api-mode.gradle.kts | 23 +++ .../src/main/kotlin/dokka-module.gradle.kts | 61 ++++++ .../src/main/kotlin/kordex-module.gradle.kts | 58 ++++++ .../src/main/kotlin/ksp-module.gradle.kts | 18 ++ .../main/kotlin/published-module.gradle.kts | 45 +++++ .../src/main/kotlin/tested-module.gradle.kts | 17 ++ extra-modules/extra-common/build.gradle.kts | 89 +-------- extra-modules/extra-mappings/build.gradle.kts | 90 +-------- gradle.properties | 3 + kord-extensions/build.gradle.kts | 180 +----------------- modules/java-time/build.gradle.kts | 161 +--------------- modules/time4j/build.gradle.kts | 160 +--------------- modules/unsafe/build.gradle.kts | 168 +--------------- settings.gradle.kts | 19 +- token-parser/build.gradle.kts | 160 +--------------- 21 files changed, 342 insertions(+), 1251 deletions(-) create mode 100644 .idea/GradleUpdaterPlugin.xml create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/GitTools.kt create mode 100644 buildSrc/src/main/kotlin/disable-explicit-api-mode.gradle.kts create mode 100644 buildSrc/src/main/kotlin/dokka-module.gradle.kts create mode 100644 buildSrc/src/main/kotlin/kordex-module.gradle.kts create mode 100644 buildSrc/src/main/kotlin/ksp-module.gradle.kts create mode 100644 buildSrc/src/main/kotlin/published-module.gradle.kts create mode 100644 buildSrc/src/main/kotlin/tested-module.gradle.kts diff --git a/.idea/GradleUpdaterPlugin.xml b/.idea/GradleUpdaterPlugin.xml new file mode 100644 index 0000000000..6ed5855a3a --- /dev/null +++ b/.idea/GradleUpdaterPlugin.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 6f6540bdc8..dfaf95a0b3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -2,9 +2,6 @@ - - - diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts index f43e049cb0..e24dc4d2ba 100644 --- a/annotation-processor/build.gradle.kts +++ b/annotation-processor/build.gradle.kts @@ -1,14 +1,7 @@ -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` } dependencies { @@ -23,135 +16,10 @@ dependencies { detektPlugins(libs.detekt) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - } -} - -detekt { - buildUponDefaultConfig = true - config = files("$rootDir/detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { +dokkaModule { moduleName.set("Kord Extensions: Annotation Processor") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Java Time") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/annotation-processor/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } } -tasks.build { - this.finalizedBy(sourceJar, javadocJar) +kordex { + jvmTarget.set("1.8") } diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts index 4b1424c70f..6e28ddcbe2 100644 --- a/annotations/build.gradle.kts +++ b/annotations/build.gradle.kts @@ -1,15 +1,7 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` } dependencies { @@ -18,135 +10,10 @@ dependencies { detektPlugins(libs.detekt) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - } -} - -detekt { - buildUponDefaultConfig = true - config = files("$rootDir/detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { +dokkaModule { moduleName.set("Kord Extensions: Annotation Processor") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Java Time") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/annotation-processor/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } } -tasks.build { - this.finalizedBy(sourceJar, javadocJar) +kordex { + jvmTarget.set("1.8") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..9aabad9254 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` +} + +repositories { + google() + gradlePluginPortal() +} + +dependencies { + implementation(kotlin("gradle-plugin", version = "1.5.30")) + implementation(kotlin("serialization", version = "1.5.30")) + implementation("org.jetbrains.dokka", "dokka-gradle-plugin", "1.5.0") + implementation("com.google.devtools.ksp", "com.google.devtools.ksp.gradle.plugin", "1.5.30-1.0.0-beta08") + implementation("io.gitlab.arturbosch.detekt", "detekt-gradle-plugin", "1.17.1") + implementation(gradleApi()) + implementation(localGroovy()) +} diff --git a/buildSrc/src/main/kotlin/GitTools.kt b/buildSrc/src/main/kotlin/GitTools.kt new file mode 100644 index 0000000000..cf59b32922 --- /dev/null +++ b/buildSrc/src/main/kotlin/GitTools.kt @@ -0,0 +1,25 @@ +import org.gradle.api.Project +import java.io.ByteArrayOutputStream + +fun Project.runCommand(command: String): String { + val output = ByteArrayOutputStream() + + exec { + commandLine(command.split(" ")) + standardOutput = output + } + + return output.toString().trim() +} + +fun Project.getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 + var gitBranch = "Unknown branch" + + try { + gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") + } catch (t: Throwable) { + println(t) + } + + return gitBranch +} diff --git a/buildSrc/src/main/kotlin/disable-explicit-api-mode.gradle.kts b/buildSrc/src/main/kotlin/disable-explicit-api-mode.gradle.kts new file mode 100644 index 0000000000..be996c1466 --- /dev/null +++ b/buildSrc/src/main/kotlin/disable-explicit-api-mode.gradle.kts @@ -0,0 +1,23 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +plugins { + kotlin("jvm") +} + +kotlin { + // https://github.com/JetBrains/kotlin/pull/4598 + fixExplicitApiModeArg() + // We still need to set this, because the IntelliJ Kotlin plugin Inspections + // look for this option instead of the CLI arg + explicitApi = ExplicitApiMode.Disabled +} + +fun fixExplicitApiModeArg() { + val clazz = ExplicitApiMode.Disabled.javaClass + val field = clazz.getDeclaredField("cliOption") + + with(field) { + isAccessible = true + set(ExplicitApiMode.Disabled, "disable") + } +} diff --git a/buildSrc/src/main/kotlin/dokka-module.gradle.kts b/buildSrc/src/main/kotlin/dokka-module.gradle.kts new file mode 100644 index 0000000000..5fbe4a0c16 --- /dev/null +++ b/buildSrc/src/main/kotlin/dokka-module.gradle.kts @@ -0,0 +1,61 @@ +import java.net.URL + +plugins { + id("org.jetbrains.dokka") +} + +val dokkaModuleExtensionName = "dokkaModule" + +abstract class DokkaModuleExtension { + abstract val moduleName: Property + abstract val includes: ListProperty +} + +extensions.create(dokkaModuleExtensionName) + +tasks { + afterEvaluate { + val projectDir = project.projectDir.relativeTo(rootProject.rootDir).toString() + dokkaHtml { + val extension = project.extensions.getByName(dokkaModuleExtensionName) + extension.moduleName.orNull?.let { + moduleName.set(it) + } + + dokkaSourceSets { + configureEach { + includeNonPublic.set(false) + skipDeprecated.set(false) + extension.moduleName.orNull?.let { + displayName.set(it) + } + extension.includes.orNull?.let { + includes.from(*it.toTypedArray()) + } + jdkVersion.set(8) + + sourceLink { + localDirectory.set(file("${project.projectDir}/src/main/kotlin")) + + remoteUrl.set( + URL( + "https://github.com/Kotlin-Discord/kord-extensions/" + + "tree/${getCurrentGitBranch()}/${projectDir}/src/main/kotlin" + ) + ) + + remoteLineSuffix.set("#L") + } + + externalDocumentationLink { + url.set(URL("http://kordlib.github.io/kord/common/common/")) + } + + externalDocumentationLink { + url.set(URL("http://kordlib.github.io/kord/core/core/")) + } + } + } + } + } +} diff --git a/buildSrc/src/main/kotlin/kordex-module.gradle.kts b/buildSrc/src/main/kotlin/kordex-module.gradle.kts new file mode 100644 index 0000000000..7ed01f4177 --- /dev/null +++ b/buildSrc/src/main/kotlin/kordex-module.gradle.kts @@ -0,0 +1,58 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") + + id("io.gitlab.arturbosch.detekt") +} + +abstract class KordexExtension { + abstract val jvmTarget: Property + abstract val javaVersion: Property +} + +val kordexExtensionName = "kordex" + +extensions.create(kordexExtensionName) + +val sourceJar = task("sourceJar", Jar::class) { + dependsOn(tasks["classes"]) + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) +} + +val javadocJar = task("javadocJar", Jar::class) { + dependsOn("dokkaJavadoc") + archiveClassifier.set("javadoc") + from(tasks.javadoc) +} + +tasks { + build { + finalizedBy(sourceJar, javadocJar) + } + kotlin { + explicitApi() + } + + afterEvaluate { + val extension = project.extensions.getByName(kordexExtensionName) + java { + sourceCompatibility = extension.javaVersion.getOrElse(JavaVersion.VERSION_1_8) + } + withType().configureEach { + kotlinOptions { + jvmTarget = extension.jvmTarget.getOrElse("1.8") + } + } + } + +} + +detekt { + buildUponDefaultConfig = true + config = files("$rootDir/detekt.yml") + + autoCorrect = true +} diff --git a/buildSrc/src/main/kotlin/ksp-module.gradle.kts b/buildSrc/src/main/kotlin/ksp-module.gradle.kts new file mode 100644 index 0000000000..e1c3aa0a8b --- /dev/null +++ b/buildSrc/src/main/kotlin/ksp-module.gradle.kts @@ -0,0 +1,18 @@ +plugins { + java + id("com.google.devtools.ksp") +} + +sourceSets { + main { + java { + srcDir(file("$buildDir/generated/ksp/main/kotlin/")) + } + } + + test { + java { + srcDir(file("$buildDir/generated/ksp/test/kotlin/")) + } + } +} diff --git a/buildSrc/src/main/kotlin/published-module.gradle.kts b/buildSrc/src/main/kotlin/published-module.gradle.kts new file mode 100644 index 0000000000..30419fe9d5 --- /dev/null +++ b/buildSrc/src/main/kotlin/published-module.gradle.kts @@ -0,0 +1,45 @@ +import org.gradle.api.publish.maven.MavenPublication + +plugins { + `maven-publish` + signing +} + +val sourceJar: Task by tasks.getting +val javadocJar: Task by tasks.getting + +publishing { + repositories { + maven { + name = "KotDis" + + url = if (project.version.toString().contains("SNAPSHOT")) { + uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") + } else { + uri("https://maven.kotlindiscord.com/repository/maven-releases/") + } + + credentials { + username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") + password = project.findProperty("kotdis.password") as String? + ?: System.getenv("KOTLIN_DISCORD_PASSWORD") + } + + version = project.version + } + } + + publications { + create("maven") { + from(components.getByName("java")) + + artifact(sourceJar) + artifact(javadocJar) + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +} diff --git a/buildSrc/src/main/kotlin/tested-module.gradle.kts b/buildSrc/src/main/kotlin/tested-module.gradle.kts new file mode 100644 index 0000000000..0530c19976 --- /dev/null +++ b/buildSrc/src/main/kotlin/tested-module.gradle.kts @@ -0,0 +1,17 @@ +plugins { + java +} + +tasks { + test { + useJUnitPlatform() + + testLogging.showStandardStreams = true + + testLogging { + events("PASSED", "FAILED", "SKIPPED", "STANDARD_OUT", "STANDARD_ERROR") + } + + systemProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug") + } +} diff --git a/extra-modules/extra-common/build.gradle.kts b/extra-modules/extra-common/build.gradle.kts index 259c0c7f53..2d2eb52dd8 100644 --- a/extra-modules/extra-common/build.gradle.kts +++ b/extra-modules/extra-common/build.gradle.kts @@ -1,12 +1,9 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - `maven-publish` - signing - - id("io.gitlab.arturbosch.detekt") + `kordex-module` + `published-module` + `dokka-module` + `disable-explicit-api-mode` - kotlin("jvm") kotlin("plugin.serialization") } @@ -28,79 +25,7 @@ dependencies { implementation(project(":kord-extensions")) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -detekt { - buildUponDefaultConfig = true - config = rootProject.files("detekt.yml") - - autoCorrect = true -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "9" - } -} - -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -tasks.build { - this.finalizedBy(sourceJar) -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) } diff --git a/extra-modules/extra-mappings/build.gradle.kts b/extra-modules/extra-mappings/build.gradle.kts index 7594ee1f2e..09670d3828 100644 --- a/extra-modules/extra-mappings/build.gradle.kts +++ b/extra-modules/extra-mappings/build.gradle.kts @@ -1,12 +1,8 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - `maven-publish` - signing - - id("io.gitlab.arturbosch.detekt") - - kotlin("jvm") + `kordex-module` + `published-module` + `dokka-module` + `disable-explicit-api-mode` } repositories { @@ -59,79 +55,7 @@ dependencies { group = "com.kotlindiscord.kord.extensions" -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -detekt { - buildUponDefaultConfig = true - config = rootProject.files("detekt.yml") - - autoCorrect = true -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "9" - } -} - -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -tasks.build { - this.finalizedBy(sourceJar) -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) } diff --git a/gradle.properties b/gradle.properties index 41f95ef885..8bb6c53762 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,3 +4,6 @@ kotlin.incremental = true ksp.incremental = false projectVersion = 1.5.0-SNAPSHOT + +#dokka will run out of memory with the default meta space +org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m diff --git a/kord-extensions/build.gradle.kts b/kord-extensions/build.gradle.kts index 00608bf1fc..7801faa26f 100644 --- a/kord-extensions/build.gradle.kts +++ b/kord-extensions/build.gradle.kts @@ -1,6 +1,4 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL buildscript { repositories { @@ -12,14 +10,11 @@ buildscript { } plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("com.google.devtools.ksp") - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` + `tested-module` + `ksp-module` } dependencies { @@ -48,169 +43,12 @@ dependencies { ksp(project(":annotation-processor")) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - } -} - -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -detekt { - buildUponDefaultConfig = true - config = files("../detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? - ?: System.getenv("KOTLIN_DISCORD_USER") - - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { - moduleName.set("Kord Extensions") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/kord-extensions/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } -} - -tasks.test { - useJUnitPlatform() - - testLogging.showStandardStreams = true - - testLogging { - events("PASSED", "FAILED", "SKIPPED", "STANDARD_OUT", "STANDARD_ERROR") - } - - systemProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug") -} - -tasks.build { - this.finalizedBy(sourceJar, javadocJar) -} - val compileKotlin: KotlinCompile by tasks compileKotlin.kotlinOptions { languageVersion = "1.5" } + +dokkaModule { + includes.add("packages.md") +} diff --git a/modules/java-time/build.gradle.kts b/modules/java-time/build.gradle.kts index 9460c9297f..48d9059e32 100644 --- a/modules/java-time/build.gradle.kts +++ b/modules/java-time/build.gradle.kts @@ -1,16 +1,10 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing + `kordex-module` + `published-module` + `dokka-module` + `ksp-module` - kotlin("jvm") kotlin("plugin.serialization") - - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") } dependencies { @@ -24,149 +18,12 @@ dependencies { testImplementation(libs.logback) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "9" - } -} - -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -detekt { - buildUponDefaultConfig = true - config = files("../../detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { - moduleName.set("Kord Extensions: Java Time") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Java Time") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/java-time/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) } -tasks.build { - this.finalizedBy(sourceJar, javadocJar) +dokkaModule { + moduleName.set("Kord Extensions: Java Time") } diff --git a/modules/time4j/build.gradle.kts b/modules/time4j/build.gradle.kts index 900019765c..b04c0e2736 100644 --- a/modules/time4j/build.gradle.kts +++ b/modules/time4j/build.gradle.kts @@ -1,15 +1,7 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` } dependencies { @@ -26,149 +18,11 @@ dependencies { testImplementation(libs.logback) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "9" - } +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) } -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -detekt { - buildUponDefaultConfig = true - config = files("../../detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { +dokkaModule { moduleName.set("Kord Extensions: Time4J") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Time4J") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/time4j/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } -} - -tasks.build { - this.finalizedBy(sourceJar, javadocJar) } diff --git a/modules/unsafe/build.gradle.kts b/modules/unsafe/build.gradle.kts index 948c0bbd55..48196d696d 100644 --- a/modules/unsafe/build.gradle.kts +++ b/modules/unsafe/build.gradle.kts @@ -1,16 +1,8 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("com.google.devtools.ksp") - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` + `ksp-module` } dependencies { @@ -26,153 +18,11 @@ dependencies { ksp(project(":annotation-processor")) } -kotlin { - explicitApi() -} - -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_9 - targetCompatibility = JavaVersion.VERSION_1_9 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "9" - } -} - -sourceSets { - main { - java { - srcDir(file("$buildDir/generated/ksp/main/kotlin/")) - } - } - - test { - java { - srcDir(file("$buildDir/generated/ksp/test/kotlin/")) - } - } -} - -detekt { - buildUponDefaultConfig = true - config = files("../../detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { - moduleName.set("Kord Extensions: Time4J") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Unsafe Module") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/modules/unsafe/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) } -tasks.build { - this.finalizedBy(sourceJar, javadocJar) +dokkaModule { + moduleName.set("Kord Extensions: Unsafe") } diff --git a/settings.gradle.kts b/settings.gradle.kts index a8c88797d8..b736e0b20d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,20 +1,5 @@ -pluginManagement { - repositories { - google() - gradlePluginPortal() - } - - plugins { - // NOTE: UPDATE THIS IF YOU UPDATE THE LIBS.VERSIONS.TOML - - kotlin("jvm") version "1.5.30" - kotlin("plugin.serialization") version "1.5.30" - - id("com.google.devtools.ksp") version "1.5.30-1.0.0-beta08" - id("io.gitlab.arturbosch.detekt") version "1.17.1" - id("org.jetbrains.dokka") version "1.4.10.2" - } -} +// NOTE: UPDATE THIS IF YOU UPDATE THE LIBS.VERSIONS.TOML +// NOTE: All the plugins and plugin repositories moved to buildSrc/build.gradle.kts rootProject.name = "kord-extensions" diff --git a/token-parser/build.gradle.kts b/token-parser/build.gradle.kts index 3e6c18931d..783bfa3612 100644 --- a/token-parser/build.gradle.kts +++ b/token-parser/build.gradle.kts @@ -1,15 +1,8 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream -import java.net.URL - plugins { - `maven-publish` - signing - - kotlin("jvm") - - id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.dokka") + `kordex-module` + `published-module` + `dokka-module` + `tested-module` } dependencies { @@ -25,147 +18,6 @@ dependencies { testImplementation(libs.logback) } -val sourceJar = task("sourceJar", Jar::class) { - dependsOn(tasks["classes"]) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - -val javadocJar = task("javadocJar", Jar::class) { - dependsOn("dokkaJavadoc") - archiveClassifier.set("javadoc") - from(tasks.javadoc) -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -kotlin { - explicitApi() -} - -tasks.withType().configureEach { - kotlinOptions { - jvmTarget = "1.8" - } -} - -detekt { - buildUponDefaultConfig = true - config = files("$rootDir/detekt.yml") - - autoCorrect = true -} - -publishing { - repositories { - maven { - name = "KotDis" - - url = if (project.version.toString().contains("SNAPSHOT")) { - uri("https://maven.kotlindiscord.com/repository/maven-snapshots/") - } else { - uri("https://maven.kotlindiscord.com/repository/maven-releases/") - } - - credentials { - username = project.findProperty("kotdis.user") as String? ?: System.getenv("KOTLIN_DISCORD_USER") - password = project.findProperty("kotdis.password") as String? - ?: System.getenv("KOTLIN_DISCORD_PASSWORD") - } - - version = project.version - } - } - - publications { - create("maven") { - from(components.getByName("java")) - - artifact(sourceJar) - artifact(javadocJar) - } - } -} - -signing { - useGpgCmd() - sign(publishing.publications["maven"]) -} - -fun runCommand(command: String): String { - val output = ByteArrayOutputStream() - - project.exec { - commandLine(command.split(" ")) - standardOutput = output - } - - return output.toString().trim() -} - -fun getCurrentGitBranch(): String { // https://gist.github.com/lordcodes/15b2a4aecbeff7c3238a70bfd20f0931 - var gitBranch = "Unknown branch" - - try { - gitBranch = runCommand("git rev-parse --abbrev-ref HEAD") - } catch (t: Throwable) { - println(t) - } - - return gitBranch -} - -tasks.dokkaHtml.configure { - moduleName.set("Kord Extensions: Annotation Processor") - - dokkaSourceSets { - configureEach { - includeNonPublic.set(false) - skipDeprecated.set(false) - - displayName.set("Kord Extensions: Java Time") - includes.from("packages.md") - jdkVersion.set(8) - - sourceLink { - localDirectory.set(file("${project.projectDir}/src/main/kotlin")) - - remoteUrl.set( - URL( - "https://github.com/Kotlin-Discord/kord-extensions/" + - "tree/${getCurrentGitBranch()}/annotation-processor/src/main/kotlin" - ) - ) - - remoteLineSuffix.set("#L") - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/common/common/")) - } - - externalDocumentationLink { - url.set(URL("http://kordlib.github.io/kord/core/core/")) - } - } - } -} - -tasks.test { - useJUnitPlatform() - - testLogging.showStandardStreams = true - - testLogging { - events("PASSED", "FAILED", "SKIPPED", "STANDARD_OUT", "STANDARD_ERROR") - } - - systemProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug") -} - -tasks.build { - this.finalizedBy(sourceJar, javadocJar) +dokkaModule { + moduleName.set("Kord Extensions: Token Parser") } From f25272eff488a3a703d4b1a73e8947979eed8f82 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 30 Sep 2021 16:30:15 +0100 Subject: [PATCH 105/131] Checks can now specify translation options --- .../extensions/checks/types/CheckContext.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index 9b058b6521..d1d95ee248 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -18,6 +18,16 @@ public class CheckContext(public val event: T, public val locale: /** Translations provider. **/ public val translations: TranslationsProvider by inject() + /** + * Translation key to use for the error response message, if not the default. + * + * The string pointed to by this variable must accept one replacement value, which is the error message itself. + */ + public var errorResponseKey: String = "checks.responseTemplate" + + /** Translation bundle used by [translate] by default and the error response translation, if not the default. **/ + public var defaultBundle: String? = null + /** Human-readable message for the user, if any. **/ public var message: String? = null @@ -132,7 +142,11 @@ public class CheckContext(public val event: T, public val locale: } /** Quick access to translate strings using this check context's [locale]. **/ - public fun translate(key: String, bundle: String? = null, replacements: Array = arrayOf()): String = + public fun translate( + key: String, + bundle: String? = defaultBundle, + replacements: Array = arrayOf() + ): String = translations.translate(key, locale, bundleName = bundle, replacements = replacements) /** @@ -142,7 +156,7 @@ public class CheckContext(public val event: T, public val locale: public fun throwIfFailedWithMessage() { if (passed.not() && message != null) { throw DiscordRelayedException( - translate("checks.responseTemplate", replacements = arrayOf(message)) + translate(errorResponseKey, defaultBundle, replacements = arrayOf(message)) ) } } From 8a922fe6ca1e15ae058b9da4e34a930956cf13aa Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 30 Sep 2021 16:44:40 +0100 Subject: [PATCH 106/131] Polish/Russian locale entries --- .../extensions/checks/types/CheckContext.kt | 3 ++ .../kord/extensions/i18n/SupportedLocales.kt | 43 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index d1d95ee248..2491a9781d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -22,6 +22,9 @@ public class CheckContext(public val event: T, public val locale: * Translation key to use for the error response message, if not the default. * * The string pointed to by this variable must accept one replacement value, which is the error message itself. + * + * **Note:** This *must* be a translation key. A bare string may not work, as the error response function uses + * the replacement functionality of the translations system. */ public var errorResponseKey: String = "checks.responseTemplate" diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt index 7e55b03ae1..18c3009c6b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt @@ -14,44 +14,59 @@ public object SupportedLocales { public val FINNISH: Locale = Locale("fi", "fi") public val FRENCH: Locale = Locale("fr", "fr") public val GERMAN: Locale = Locale("de", "de") + public val POLISH: Locale = Locale("pl", "pl") public val PORTUGUESE: Locale = Locale("pt", "pt") + public val RUSSIAN: Locale = Locale("ru", "ru") public val ALL_LOCALES: Map = mapOf( - "中文" to CHINESE_SIMPLIFIED, - "汉语" to CHINESE_SIMPLIFIED, - "普通话" to CHINESE_SIMPLIFIED, - "简体中文" to CHINESE_SIMPLIFIED, "chinese" to CHINESE_SIMPLIFIED, "zh" to CHINESE_SIMPLIFIED, "zh_cn" to CHINESE_SIMPLIFIED, + "中文" to CHINESE_SIMPLIFIED, + "普通话" to CHINESE_SIMPLIFIED, + "汉语" to CHINESE_SIMPLIFIED, + "简体中文" to CHINESE_SIMPLIFIED, - "english" to ENGLISH, "en" to ENGLISH, "en_gb" to ENGLISH, "en_us" to ENGLISH, + "english" to ENGLISH, + "fi" to FINNISH, + "fi_fi" to FINNISH, + "finnish" to FINNISH, "suomen kieli" to FINNISH, "suomen" to FINNISH, "suomi" to FINNISH, - "finnish" to FINNISH, - "fi" to FINNISH, - "fi_fi" to FINNISH, - "français" to FRENCH, - "francais" to FRENCH, - "french" to FRENCH, "fr" to FRENCH, "fr_fr" to FRENCH, + "francais" to FRENCH, + "français" to FRENCH, + "french" to FRENCH, - "deutsch" to GERMAN, - "german" to GERMAN, "de" to GERMAN, "de_de" to GERMAN, + "deutsch" to GERMAN, + "german" to GERMAN, - "português" to PORTUGUESE, "portugues" to PORTUGUESE, "portuguese" to PORTUGUESE, + "português" to PORTUGUESE, "pt" to PORTUGUESE, "pt_pt" to PORTUGUESE, + + "pl" to POLISH, + "pl_pl" to POLISH, + "polish" to POLISH, + "polska" to POLISH, + "polskie" to POLISH, + + "ru" to RUSSIAN, + "ru_ru" to RUSSIAN, + "russian" to RUSSIAN, + "русская" to RUSSIAN, + "русскии" to RUSSIAN, + "русский" to RUSSIAN, ) } From 3d8e1e96da00df53c735352fb9ddb847dbc3f030 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 30 Sep 2021 16:46:10 +0100 Subject: [PATCH 107/131] Update crowdin links to weblate --- .../commands/converters/impl/SupportedLocaleConverter.kt | 2 +- .../com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt index e06eb39b3f..a98c53456e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt @@ -30,7 +30,7 @@ import java.util.* * with them, rather than a more general converter. * * If the locale you want to use isn't supported yet, feel free to contribute translations for it to - * [our CrowdIn project](https://crowdin.com/project/kordex). + * [our Weblate project](https://hosted.weblate.org/projects/kord-extensions/main/). */ @Converter( "supportedLocale", diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt index 18c3009c6b..f80d8908c6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/SupportedLocales.kt @@ -6,7 +6,7 @@ import java.util.* * List of supported locales. These are locales with **merged** translations. * * If you've written a translation, don't try to modify this using reflection or something - instead, contribute it - * back: https://crowdin.com/project/kordex + * back: https://hosted.weblate.org/projects/kord-extensions/main/ */ public object SupportedLocales { public val CHINESE_SIMPLIFIED: Locale = Locale("zh", "cn") From 27b33805e9f1de70b606f43282db839b60340eaf Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 30 Sep 2021 17:10:52 +0100 Subject: [PATCH 108/131] [#91] Command/component error customisation --- .../builders/ExtensibleBotBuilder.kt | 17 ++++ .../extensions/checks/types/CheckContext.kt | 10 ++- .../message/EphemeralMessageCommand.kt | 4 +- .../message/PublicMessageCommand.kt | 4 +- .../slash/EphemeralSlashCommand.kt | 4 +- .../application/slash/PublicSlashCommand.kt | 4 +- .../application/user/EphemeralUserCommand.kt | 4 +- .../application/user/PublicUserCommand.kt | 4 +- .../extensions/commands/chat/ChatCommand.kt | 90 +++++++++++-------- .../buttons/EphemeralInteractionButton.kt | 4 +- .../buttons/PublicInteractionButton.kt | 4 +- .../components/menus/EphemeralSelectMenu.kt | 4 +- .../components/menus/PublicSelectMenu.kt | 4 +- .../extensions/impl/SentryExtension.kt | 4 +- 14 files changed, 101 insertions(+), 60 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d16f1a3ef7..46c0d7b10b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -37,6 +37,7 @@ import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.Intents import dev.kord.gateway.builder.PresenceBuilder +import dev.kord.rest.builder.message.create.MessageCreateBuilder import mu.KLogger import mu.KotlinLogging import org.koin.core.context.startKoin @@ -67,6 +68,14 @@ public open class ExtensibleBotBuilder { /** @suppress Builder that shouldn't be set directly by the user. **/ public val componentsBuilder: ComponentsBuilder = ComponentsBuilder() + /** + * Message builder responsible for formatting error responses that are sent to users during command and component + * body execution. + */ + public var errorResponseBuilder: suspend (MessageCreateBuilder).(message: String) -> Unit = { message -> + content = message + } + /** @suppress Builder that shouldn't be set directly by the user. **/ public open val extensionsBuilder: ExtensionsBuilder = ExtensionsBuilder() @@ -133,6 +142,14 @@ public open class ExtensibleBotBuilder { builder(componentsBuilder) } + /** + * Register the message builder responsible for formatting error responses, which are sent to users during command + * and component body execution. + */ + public fun errorResponse(builder: suspend (MessageCreateBuilder).(message: String) -> Unit) { + errorResponseBuilder = builder + } + /** * DSL function used to insert code at various points in the bot's lifecycle. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt index 2491a9781d..e25bb886b6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/types/CheckContext.kt @@ -159,8 +159,16 @@ public class CheckContext(public val event: T, public val locale: public fun throwIfFailedWithMessage() { if (passed.not() && message != null) { throw DiscordRelayedException( - translate(errorResponseKey, defaultBundle, replacements = arrayOf(message)) + getTranslatedMessage()!! ) } } + + /** Get the translated check failure message, if the check has failed and a message was set. **/ + public fun getTranslatedMessage(): String? = + if (passed.not() && message != null) { + translate(errorResponseKey, defaultBundle, replacements = arrayOf(message)) + } else { + null + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index e3a2745b5b..84bf76dc68 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -44,7 +44,7 @@ public class EphemeralMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync(EphemeralMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -89,6 +89,6 @@ public class EphemeralMessageCommand( } override suspend fun respondText(context: EphemeralMessageCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 8fb3104394..86253b20b7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -45,7 +45,7 @@ public class PublicMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -90,6 +90,6 @@ public class PublicMessageCommand( } override suspend fun respondText(context: PublicMessageCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index 86622a874c..322eb729bb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -75,7 +75,7 @@ public class EphemeralSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync( EphemeralSlashCommandFailedChecksEvent( @@ -145,6 +145,6 @@ public class EphemeralSlashCommand( } override suspend fun respondText(context: EphemeralSlashCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 702a1f0022..e37bced8f4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -76,7 +76,7 @@ public class PublicSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -133,6 +133,6 @@ public class PublicSlashCommand( } override suspend fun respondText(context: PublicSlashCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index a67822470c..768d4b4589 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -44,7 +44,7 @@ public class EphemeralUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) @@ -87,6 +87,6 @@ public class EphemeralUserCommand( } override suspend fun respondText(context: EphemeralUserCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 60429af122..5179bd0e8d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -45,7 +45,7 @@ public class PublicUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) @@ -88,6 +88,6 @@ public class PublicUserCommand( } override suspend fun respondText(context: PublicUserCommandContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index cacfb359d0..25c4cef7bd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.ArgumentParsingException import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.InvalidCommandException import com.kotlindiscord.kord.extensions.annotations.ExtensionDSL +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.Arguments @@ -54,6 +55,9 @@ public open class ChatCommand( /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() + /** Bot settings object. **/ + public val settings: ExtensibleBotBuilder by inject() + /** Message command registry. **/ public val registry: ChatCommandRegistry by inject() @@ -264,15 +268,12 @@ public open class ChatCommand( check(context) if (!context.passed) { - val message = context.message + val message = context.getTranslatedMessage() if (message != null && sendMessage) { - event.message.respond( - translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - ) + event.message.respond { + settings.errorResponseBuilder(this, message) + } } return false @@ -289,12 +290,9 @@ public open class ChatCommand( val message = context.message if (message != null && sendMessage) { - event.message.respond( - translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - ) + event.message.respond { + settings.errorResponseBuilder(this, message) + } } return false @@ -310,12 +308,9 @@ public open class ChatCommand( val message = context.message if (message != null && sendMessage) { - event.message.respond( - translationsProvider.translate( - "checks.responseTemplate", - replacements = arrayOf(message) - ) - ) + event.message.respond { + settings.errorResponseBuilder(this, message) + } } return false @@ -392,7 +387,10 @@ public open class ChatCommand( } } catch (e: DiscordRelayedException) { emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) - event.message.respond(e.reason) + + event.message.respond { + settings.errorResponseBuilder(this, e.reason) + } return@withLock } @@ -430,7 +428,10 @@ public open class ChatCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - event.message.respond(e.reason) + event.message.respond { + settings.errorResponseBuilder(this, e.reason) + } + emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) return@withLock @@ -441,7 +442,10 @@ public open class ChatCommand( val parsedArgs = registry.parser.parse(this.arguments!!, context) context.populateArgs(parsedArgs) } catch (e: ArgumentParsingException) { - event.message.respond(e.reason) + event.message.respond { + settings.errorResponseBuilder(this, e.reason) + } + emitEventAsync(ChatCommandFailedParsingEvent(this, event, e)) return@withLock @@ -452,7 +456,9 @@ public open class ChatCommand( this.body(context) } catch (t: Throwable) { if (t is DiscordRelayedException) { - event.message.respond(t.reason) + event.message.respond { + settings.errorResponseBuilder(this, t.reason) + } } emitEventAsync(ChatCommandFailedWithExceptionEvent(this, event, t)) @@ -495,27 +501,37 @@ public open class ChatCommand( if (extension.bot.extensions.containsKey("sentry")) { val prefix = registry.getPrefix(event) - event.message.respond( - context.translate( - "commands.error.user.sentry.message", - null, - replacements = arrayOf( - prefix, - sentryId + event.message.respond { + settings.errorResponseBuilder( + this, + + context.translate( + "commands.error.user.sentry.message", + null, + replacements = arrayOf( + prefix, + sentryId + ) ) ) - ) + } } else { - event.message.respond( - context.translate("commands.error.user", null) - ) + event.message.respond { + settings.errorResponseBuilder( + this, + context.translate("commands.error.user", null) + ) + } } } else { logger.error(t) { "Error during execution of $name command ($event)" } - event.message.respond( - context.translate("commands.error.user", null) - ) + event.message.respond { + settings.errorResponseBuilder( + this, + context.translate("commands.error.user", null) + ) + } } return@withLock diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index 9089dfb1ed..e58c73309b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -44,7 +44,7 @@ public open class EphemeralInteractionButton( return@withLock } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } return@withLock } @@ -84,6 +84,6 @@ public open class EphemeralInteractionButton( } override suspend fun respondText(context: EphemeralInteractionButtonContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 285068eb5e..52839a727f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -45,7 +45,7 @@ public open class PublicInteractionButton( return@withLock } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } return@withLock } @@ -85,6 +85,6 @@ public open class PublicInteractionButton( } override suspend fun respondText(context: PublicInteractionButtonContext, message: String) { - context.respond { content = message } + context.respond { settings.errorResponseBuilder(this, message) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index fe3ade44b0..e6397365f7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -30,7 +30,7 @@ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu Date: Thu, 30 Sep 2021 17:12:46 +0100 Subject: [PATCH 109/131] Small DSL fixes --- .../kord/extensions/builders/ExtensibleBotBuilder.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 46c0d7b10b..d712b106f0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -69,8 +69,7 @@ public open class ExtensibleBotBuilder { public val componentsBuilder: ComponentsBuilder = ComponentsBuilder() /** - * Message builder responsible for formatting error responses that are sent to users during command and component - * body execution. + * @suppress Builder that shouldn't be set directly by the user. */ public var errorResponseBuilder: suspend (MessageCreateBuilder).(message: String) -> Unit = { message -> content = message @@ -146,6 +145,7 @@ public open class ExtensibleBotBuilder { * Register the message builder responsible for formatting error responses, which are sent to users during command * and component body execution. */ + @BotBuilderDSL public fun errorResponse(builder: suspend (MessageCreateBuilder).(message: String) -> Unit) { errorResponseBuilder = builder } From 6427a7b130d00bf3cc8f4ce58fb5d754c594917f Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sat, 2 Oct 2021 09:45:58 +0200 Subject: [PATCH 110/131] Add self member tools (#98) --- .../kord/extensions/utils/_Guilds.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Guilds.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Guilds.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Guilds.kt new file mode 100644 index 0000000000..ef6d673fd3 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Guilds.kt @@ -0,0 +1,40 @@ +package com.kotlindiscord.kord.extensions.utils + +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.core.Kord +import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.entity.Member +import dev.kord.core.entity.channel.GuildChannel + +/** + * Retrieves the member of the bot itself on this [GuildBehavior]. + * + * @see Kord.selfId + */ +public suspend fun GuildBehavior.selfMember(): Member = getMember(kord.selfId) + +/** + * Checks whether the bot has at least [requiredPermissions] in this [GuildChannel]. + * + * @see GuildBehavior.botHasPermissions + */ +public suspend fun GuildChannel.botHasPermissions(vararg requiredPermissions: Permission): Boolean = + guild.botHasPermissions(this, Permissions(requiredPermissions.asIterable())) + +/** + * Checks whether the bot globally has at least [requiredPermissions] on this guild. + * + * @see GuildChannel.botHasPermissions + */ +public suspend fun GuildBehavior.botHasPermissions(vararg requiredPermissions: Permission): Boolean = + botHasPermissions(null, Permissions(requiredPermissions.asIterable())) + +private suspend fun GuildBehavior.botHasPermissions(channel: GuildChannel?, requiredPermissions: Permissions): Boolean { + val selfMember = selfMember() + val effectivePermissions = + channel?.run { permissionsForMember(selfMember) } + ?: selfMember.getPermissions() + + return requiredPermissions in effectivePermissions +} From 6ba3c7ccd8e7ad7138b7641bb06ee75ba6ffcce9 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 2 Oct 2021 15:27:06 +0100 Subject: [PATCH 111/131] Translate and lowercase slash command arguments --- .../application/ApplicationCommandRegistry.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) 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 f4973105e0..ef95d350f1 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 @@ -746,8 +746,13 @@ public open class ApplicationCommandRegistry : KoinComponent { if (this.options == null) this.options = mutableListOf() - // TODO: It's impossible to translate these right now - this.options!! += converter.toSlashOption(arg) + val option = converter.toSlashOption(arg) + + option.name = translationsProvider + .translate(option.name, locale, converter.bundle) + .lowercase() + + this.options!! += option } } } else { @@ -759,8 +764,13 @@ public open class ApplicationCommandRegistry : KoinComponent { error("Argument ${arg.displayName} does not support slash commands.") } - // TODO: It's impossible to translate these right now - converter.toSlashOption(arg) + val option = converter.toSlashOption(arg) + + option.name = translationsProvider + .translate(option.name, locale, converter.bundle) + .lowercase() + + option } this.subCommand( @@ -785,7 +795,12 @@ public open class ApplicationCommandRegistry : KoinComponent { error("Argument ${arg.displayName} does not support slash commands.") } - // TODO: It's impossible to translate these right now + val option = converter.toSlashOption(arg) + + option.name = translationsProvider + .translate(option.name, locale, converter.bundle) + .lowercase() + converter.toSlashOption(arg) } From c687ccb993451a1b33041852234669d4ed6f97ab Mon Sep 17 00:00:00 2001 From: Alex <2230292+ByteAlex@users.noreply.github.com> Date: Sat, 2 Oct 2021 17:07:43 +0200 Subject: [PATCH 112/131] Extensions to see if a member/role "canInteract" with another Member/Role (#99) --- .../kord/extensions/utils/_Member.kt | 39 +++++++++++++++++++ .../kord/extensions/utils/_Role.kt | 16 ++++++++ 2 files changed, 55 insertions(+) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Role.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt index 8d4a778934..adf971690e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt @@ -1,6 +1,7 @@ package com.kotlindiscord.kord.extensions.utils import dev.kord.common.entity.Permission +import dev.kord.core.entity.Guild import dev.kord.core.entity.Member import dev.kord.core.entity.Role import kotlinx.coroutines.flow.toList @@ -85,3 +86,41 @@ public suspend fun Member.hasPermissions(perms: Collection): Boolean perms.all { it in permissions } } + +/** + * Checks if this [Member] can interact (delete/edit/assign/..) with the specified [Role]. + * + * This checks if the [Member] has any role which is higher in hierarchy than [Role]. + * The logic also accounts for [Guild] ownership. + * + * Throws an [IllegalArgumentException] if the role is from a different guild. + */ +public suspend fun Member.canInteract(role: Role): Boolean { + val guild = getGuild() + + if (guild.ownerId == this.id) return true + + val highestRole = getTopRole() ?: guild.getEveryoneRole() + return highestRole.canInteract(role) +} + +/** + * Checks if this [Member] can interact (kick/ban/..) with another [Member] + * + * This checks if the [Member] has any role which is higher in hierarchy than all [Role]s of the + * specified [Member] + * The logic also accounts for [Guild] ownership + * + * Throws an [IllegalArgumentException] if the member is from a different guild. + */ +public suspend fun Member.canInteract(member: Member): Boolean { + val guild = getGuild() + + if (isOwner()) return true + if (member.isOwner()) return false + + val highestRole = getTopRole() ?: guild.getEveryoneRole() + val otherHighestRole = member.getTopRole() ?: guild.getEveryoneRole() + + return highestRole.canInteract(otherHighestRole) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Role.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Role.kt new file mode 100644 index 0000000000..00741ada4e --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Role.kt @@ -0,0 +1,16 @@ +package com.kotlindiscord.kord.extensions.utils + +import dev.kord.core.entity.Role + +/** + * Checks whether a [Role] can interact with another [Role] by comparing their [rawPosition]s. + * + * Throws an [IllegalArgumentException] when the roles are not from the same guild. + */ +public fun Role.canInteract(role: Role): Boolean { + if (role.guildId != guildId) { + throw IllegalArgumentException("canInteract can only be called within the same guild!") + } + + return role.rawPosition < rawPosition +} From e64c8c19e3ae0510d702fc8c882e107a474d960e Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 2 Oct 2021 16:57:32 +0100 Subject: [PATCH 113/131] Overrides in resource bundle translations --- .../i18n/ResourceBundleTranslations.kt | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt index 8281026881..31f7707104 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/i18n/ResourceBundleTranslations.kt @@ -26,12 +26,14 @@ public class ResourceBundleTranslations( ) private val bundles: MutableMap, ResourceBundle> = mutableMapOf() + private val overrideBundles: MutableMap, ResourceBundle> = mutableMapOf() public override fun hasKey(key: String, locale: Locale, bundleName: String?): Boolean { return try { - val bundleObj = getBundle(locale, bundleName) + val (bundle, _) = getBundles(locale, bundleName) - bundleObj.keys.toList().contains(key) + // Overrides aren't for adding keys, so we don't check them + bundle.keys.toList().contains(key) } catch (e: MissingResourceException) { logger.trace { "Failed to get bundle $bundleName for locale $locale" } @@ -40,7 +42,7 @@ public class ResourceBundleTranslations( } @Throws(MissingResourceException::class) - private fun getBundle(locale: Locale, bundleName: String?): ResourceBundle { + private fun getBundles(locale: Locale, bundleName: String?): Pair { var bundle = "translations." + (bundleName ?: KORDEX_KEY) if (bundle.count { it == '.' } < 2) { @@ -49,15 +51,28 @@ public class ResourceBundleTranslations( val bundleKey = bundle to locale - logger.trace { "Getting bundle $bundleKey for locale $locale" } - bundles[bundleKey] = bundles[bundleKey] ?: ResourceBundle.getBundle(bundle, locale, Control) + if (bundles[bundleKey] == null) { + logger.trace { "Getting bundle $bundle for locale $locale" } + bundles[bundleKey] = ResourceBundle.getBundle(bundle, locale, Control) - return bundles[bundleKey]!! + try { + val overrideBundle = bundle + "_override" + + logger.trace { "Getting override bundle $overrideBundle for locale $locale" } + + overrideBundles[bundleKey] = ResourceBundle.getBundle(overrideBundle, locale, Control) + } catch (e: MissingResourceException) { + logger.trace { "No override bundle found." } + } + } + + return bundles[bundleKey]!! to overrideBundles[bundleKey] } @Throws(MissingResourceException::class) public override fun get(key: String, locale: Locale, bundleName: String?): String { - val result = getBundle(locale, bundleName).getString(key) + val (bundle, overrideBundle) = getBundles(locale, bundleName) + val result = overrideBundle?.getStringOrNull(key) ?: bundle.getString(key) logger.trace { "Result: $key -> $result" } @@ -95,6 +110,14 @@ public class ResourceBundleTranslations( } } + private fun ResourceBundle.getStringOrNull(key: String): String? { + return try { + getString(key) + } catch (e: MissingResourceException) { + null + } + } + private object Control : ResourceBundle.Control() { override fun getFormats(baseName: String?): MutableList { if (baseName == null) { From e03bffbbbf2f7b377d37b4248b191ce720dd1d2e Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 2 Oct 2021 17:48:31 +0100 Subject: [PATCH 114/131] Failure responses get types/exceptions --- .../builders/ExtensibleBotBuilder.kt | 12 ++-- .../message/EphemeralMessageCommand.kt | 22 +++++-- .../application/message/MessageCommand.kt | 11 +++- .../message/PublicMessageCommand.kt | 22 +++++-- .../slash/EphemeralSlashCommand.kt | 24 +++++--- .../application/slash/PublicSlashCommand.kt | 24 +++++--- .../application/slash/SlashCommand.kt | 11 +++- .../application/user/EphemeralUserCommand.kt | 22 +++++-- .../application/user/PublicUserCommand.kt | 22 +++++-- .../commands/application/user/UserCommand.kt | 11 +++- .../extensions/commands/chat/ChatCommand.kt | 58 ++++++++++++++----- .../buttons/EphemeralInteractionButton.kt | 22 +++++-- .../buttons/InteractionButtonWithAction.kt | 11 +++- .../buttons/PublicInteractionButton.kt | 22 +++++-- .../components/menus/EphemeralSelectMenu.kt | 22 +++++-- .../components/menus/PublicSelectMenu.kt | 22 +++++-- .../extensions/components/menus/SelectMenu.kt | 11 +++- .../kord/extensions/types/FailureReason.kt | 38 ++++++++++++ .../unsafe/commands/UnsafeMessageCommand.kt | 29 +++++++--- .../unsafe/commands/UnsafeSlashCommand.kt | 26 ++++++--- .../unsafe/commands/UnsafeUserCommand.kt | 28 ++++++--- 21 files changed, 357 insertions(+), 113 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/FailureReason.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d712b106f0..d16c820432 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -17,6 +17,7 @@ import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations import com.kotlindiscord.kord.extensions.i18n.SupportedLocales import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.sentry.SentryAdapter +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.getKoin import com.kotlindiscord.kord.extensions.utils.loadModule import dev.kord.cache.api.DataCache @@ -54,6 +55,9 @@ internal typealias LocaleResolver = suspend ( user: UserBehavior? ) -> Locale? +internal typealias FailureResponseBuilder = + suspend (MessageCreateBuilder).(message: String, type: FailureReason<*>) -> Unit + /** * Builder class used for configuring and creating an [ExtensibleBot]. * @@ -71,9 +75,7 @@ public open class ExtensibleBotBuilder { /** * @suppress Builder that shouldn't be set directly by the user. */ - public var errorResponseBuilder: suspend (MessageCreateBuilder).(message: String) -> Unit = { message -> - content = message - } + public var failureResponseBuilder: FailureResponseBuilder = { message, _ -> content = message } /** @suppress Builder that shouldn't be set directly by the user. **/ public open val extensionsBuilder: ExtensionsBuilder = ExtensionsBuilder() @@ -146,8 +148,8 @@ public open class ExtensibleBotBuilder { * and component body execution. */ @BotBuilderDSL - public fun errorResponse(builder: suspend (MessageCreateBuilder).(message: String) -> Unit) { - errorResponseBuilder = builder + public fun errorResponse(builder: FailureResponseBuilder) { + failureResponseBuilder = builder } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index 84bf76dc68..b8c703bbee 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommand import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralMessageCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent @@ -44,7 +45,9 @@ public class EphemeralMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(EphemeralMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -66,7 +69,7 @@ public class EphemeralMessageCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(EphemeralMessageCommandFailedChecksEvent(this, event, e.reason)) return @@ -75,11 +78,14 @@ public class EphemeralMessageCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(EphemeralMessageCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(EphemeralMessageCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) return @@ -88,7 +94,11 @@ public class EphemeralMessageCommand( emitEventAsync(EphemeralMessageCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: EphemeralMessageCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: EphemeralMessageCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt index 0280ecde4f..6542683ccd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/MessageCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate @@ -47,7 +48,7 @@ public abstract class MessageCommand>( public abstract override suspend fun call(event: MessageCommandInteractionCreateEvent) /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ - public abstract suspend fun respondText(context: C, message: String) + public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) @@ -175,9 +176,13 @@ public abstract class MessageCommand>( context.translate("commands.error.user", null) } - respondText(context, errorMessage) + respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { - respondText(context, context.translate("commands.error.user", null)) + respondText( + context, + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) + ) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index 86253b20b7..bd1180ac7d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandFai import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.PublicMessageCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic @@ -45,7 +46,9 @@ public class PublicMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -67,7 +70,7 @@ public class PublicMessageCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(PublicMessageCommandFailedChecksEvent(this, event, e.reason)) return @@ -76,11 +79,14 @@ public class PublicMessageCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(PublicMessageCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(PublicMessageCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) return @@ -89,7 +95,11 @@ public class PublicMessageCommand( emitEventAsync(PublicMessageCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: PublicMessageCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: PublicMessageCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index 322eb729bb..912ce7f245 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.interaction.GroupCommand @@ -75,7 +76,9 @@ public class EphemeralSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync( EphemeralSlashCommandFailedChecksEvent( @@ -103,7 +106,7 @@ public class EphemeralSlashCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync( EphemeralSlashCommandFailedChecksEvent( @@ -121,7 +124,7 @@ public class EphemeralSlashCommand( context.populateArgs(args) } catch (e: ArgumentParsingException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.ArgumentParsingFailure(e)) emitEventAsync(EphemeralSlashCommandFailedParsingEvent(this, event, e)) return @@ -131,11 +134,14 @@ public class EphemeralSlashCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(EphemeralSlashCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(EphemeralSlashCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t, this) return @@ -144,7 +150,11 @@ public class EphemeralSlashCommand( emitEventAsync(EphemeralSlashCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: EphemeralSlashCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: EphemeralSlashCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index e37bced8f4..41cca70be2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -7,6 +7,7 @@ import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.events.* import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic @@ -76,7 +77,9 @@ public class PublicSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -98,7 +101,7 @@ public class PublicSlashCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(PublicSlashCommandFailedChecksEvent(this, event, e.reason)) return @@ -109,7 +112,7 @@ public class PublicSlashCommand( context.populateArgs(args) } catch (e: ArgumentParsingException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.ArgumentParsingFailure(e)) emitEventAsync(PublicSlashCommandFailedParsingEvent(this, event, e)) return @@ -119,11 +122,14 @@ public class PublicSlashCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(PublicSlashCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(PublicSlashCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t, this) return @@ -132,7 +138,11 @@ public class PublicSlashCommand( emitEventAsync(PublicSlashCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: PublicSlashCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: PublicSlashCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt index e8798d754f..56d6b9ec3e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommand.kt @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate @@ -130,7 +131,7 @@ public abstract class SlashCommand, A : Arguments> public abstract override suspend fun call(event: ChatInputCommandInteractionCreateEvent) /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ - public abstract suspend fun respondText(context: C, message: String) + public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) /** * Override this to implement the final calling logic, including creating the command context and running with it. @@ -271,9 +272,13 @@ public abstract class SlashCommand, A : Arguments> context.translate("commands.error.user", null) } - respondText(context, errorMessage) + respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { - respondText(context, context.translate("commands.error.user", null)) + respondText( + context, + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) + ) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index 768d4b4589..b10dc73ad8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandFai import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.EphemeralUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent @@ -44,7 +45,9 @@ public class EphemeralUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) @@ -66,7 +69,7 @@ public class EphemeralUserCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(EphemeralUserCommandFailedChecksEvent(this, event, e.reason)) return @@ -75,18 +78,25 @@ public class EphemeralUserCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(EphemeralUserCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(EphemeralUserCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) } emitEventAsync(EphemeralUserCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: EphemeralUserCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: EphemeralUserCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index 5179bd0e8d..a29f03de4a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandFailed import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandInvocationEvent import com.kotlindiscord.kord.extensions.commands.events.PublicUserCommandSucceededEvent import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic @@ -45,7 +46,9 @@ public class PublicUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) @@ -67,7 +70,7 @@ public class PublicUserCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(PublicUserCommandFailedChecksEvent(this, event, e.reason)) return @@ -76,18 +79,25 @@ public class PublicUserCommand( try { body(context) } catch (t: Throwable) { + emitEventAsync(PublicUserCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) + + return } - emitEventAsync(PublicUserCommandFailedWithExceptionEvent(this, event, t)) handleError(context, t) } emitEventAsync(PublicUserCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: PublicUserCommandContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: PublicUserCommandContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt index 0ebb2a31d3..4bdb353f06 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/UserCommand.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.translate @@ -47,7 +48,7 @@ public abstract class UserCommand>( public abstract override suspend fun call(event: UserCommandInteractionCreateEvent) /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ - public abstract suspend fun respondText(context: C, message: String) + public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) /** Checks whether the bot has the specified required permissions, throwing if it doesn't. **/ @Throws(DiscordRelayedException::class) @@ -175,9 +176,13 @@ public abstract class UserCommand>( context.translate("commands.error.user", null) } - respondText(context, errorMessage) + respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { - respondText(context, context.translate("commands.error.user", null)) + respondText( + context, + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) + ) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt index 25c4cef7bd..6bfedd4ec9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/chat/ChatCommand.kt @@ -20,6 +20,7 @@ import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.SentryAdapter import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember import com.kotlindiscord.kord.extensions.utils.respond @@ -272,7 +273,14 @@ public open class ChatCommand( if (message != null && sendMessage) { event.message.respond { - settings.errorResponseBuilder(this, message) + settings.failureResponseBuilder( + this, + message, + + FailureReason.ProvidedCheckFailure( + DiscordRelayedException(message, context.errorResponseKey) + ) + ) } } @@ -291,7 +299,14 @@ public open class ChatCommand( if (message != null && sendMessage) { event.message.respond { - settings.errorResponseBuilder(this, message) + settings.failureResponseBuilder( + this, + message, + + FailureReason.ProvidedCheckFailure( + DiscordRelayedException(message, context.errorResponseKey) + ) + ) } } @@ -309,7 +324,14 @@ public open class ChatCommand( if (message != null && sendMessage) { event.message.respond { - settings.errorResponseBuilder(this, message) + settings.failureResponseBuilder( + this, + message, + + FailureReason.ProvidedCheckFailure( + DiscordRelayedException(message, context.errorResponseKey) + ) + ) } } @@ -389,7 +411,7 @@ public open class ChatCommand( emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) event.message.respond { - settings.errorResponseBuilder(this, e.reason) + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) } return@withLock @@ -429,7 +451,7 @@ public open class ChatCommand( checkBotPerms(context) } catch (e: DiscordRelayedException) { event.message.respond { - settings.errorResponseBuilder(this, e.reason) + settings.failureResponseBuilder(this, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) } emitEventAsync(ChatCommandFailedChecksEvent(this, event, e.reason)) @@ -443,7 +465,7 @@ public open class ChatCommand( context.populateArgs(parsedArgs) } catch (e: ArgumentParsingException) { event.message.respond { - settings.errorResponseBuilder(this, e.reason) + settings.failureResponseBuilder(this, e.reason, FailureReason.ArgumentParsingFailure(e)) } emitEventAsync(ChatCommandFailedParsingEvent(this, event, e)) @@ -455,13 +477,15 @@ public open class ChatCommand( try { this.body(context) } catch (t: Throwable) { + emitEventAsync(ChatCommandFailedWithExceptionEvent(this, event, t)) + if (t is DiscordRelayedException) { event.message.respond { - settings.errorResponseBuilder(this, t.reason) + settings.failureResponseBuilder(this, t.reason, FailureReason.RelayedFailure(t)) } - } - emitEventAsync(ChatCommandFailedWithExceptionEvent(this, event, t)) + return@withLock + } if (sentry.enabled) { logger.trace { "Submitting error to sentry." } @@ -502,7 +526,7 @@ public open class ChatCommand( val prefix = registry.getPrefix(event) event.message.respond { - settings.errorResponseBuilder( + settings.failureResponseBuilder( this, context.translate( @@ -512,14 +536,17 @@ public open class ChatCommand( prefix, sentryId ) - ) + ), + + FailureReason.ExecutionError(t) ) } } else { event.message.respond { - settings.errorResponseBuilder( + settings.failureResponseBuilder( this, - context.translate("commands.error.user", null) + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) ) } } @@ -527,9 +554,10 @@ public open class ChatCommand( logger.error(t) { "Error during execution of $name command ($event)" } event.message.respond { - settings.errorResponseBuilder( + settings.failureResponseBuilder( this, - context.translate("commands.error.user", null) + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) ) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index e58c73309b..f106f8ab11 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.ButtonStyle @@ -44,7 +45,9 @@ public open class EphemeralInteractionButton( return@withLock } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } return@withLock } @@ -67,9 +70,16 @@ public open class EphemeralInteractionButton( try { checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { body(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.RelayedFailure(e)) } catch (t: Throwable) { handleError(context, t, this) } @@ -83,7 +93,11 @@ public open class EphemeralInteractionButton( } } - override suspend fun respondText(context: EphemeralInteractionButtonContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: EphemeralInteractionButtonContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt index 6e7651fb82..46a62e3634 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/InteractionButtonWithAction.kt @@ -5,6 +5,7 @@ import com.kotlindiscord.kord.extensions.components.types.HasPartialEmoji import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.DiscordPartialEmoji import dev.kord.core.entity.channel.DmChannel @@ -91,9 +92,13 @@ public abstract class InteractionButtonWithAction( context.translate("commands.error.user", null) } - respondText(context, errorMessage) + respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { - respondText(context, context.translate("commands.error.user", null)) + respondText( + context, + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) + ) } } @@ -106,5 +111,5 @@ public abstract class InteractionButtonWithAction( } /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ - public abstract suspend fun respondText(context: C, message: String) + public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 52839a727f..4b481dab2c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.ButtonStyle @@ -45,7 +46,9 @@ public open class PublicInteractionButton( return@withLock } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { settings.errorResponseBuilder(this, e.reason) } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } return@withLock } @@ -68,9 +71,16 @@ public open class PublicInteractionButton( try { checkBotPerms(context) + } catch (e: DiscordRelayedException) { + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) + + return@withLock + } + + try { body(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.RelayedFailure(e)) } catch (t: Throwable) { handleError(context, t, this) } @@ -84,7 +94,11 @@ public open class PublicInteractionButton( } } - override suspend fun respondText(context: PublicInteractionButtonContext, message: String) { - context.respond { settings.errorResponseBuilder(this, message) } + override suspend fun respondText( + context: PublicInteractionButtonContext, + message: String, + failureType: FailureReason<*> + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index e6397365f7..501fc9ecd7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.behavior.interaction.respondEphemeral @@ -30,7 +31,9 @@ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt index d2d93f56e5..e1feed68a2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/PublicSelectMenu.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.behavior.interaction.respondEphemeral @@ -31,7 +32,9 @@ public open class PublicSelectMenu(timeoutTask: Task?) : SelectMenu + ) { + context.respond { settings.failureResponseBuilder(this, message, failureType) } } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt index 03d4a59d9d..cfe66440e3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/SelectMenu.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.components.ComponentWithAction import com.kotlindiscord.kord.extensions.sentry.BreadcrumbType import com.kotlindiscord.kord.extensions.sentry.tag import com.kotlindiscord.kord.extensions.sentry.user +import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildMessageChannel @@ -169,12 +170,16 @@ public abstract class SelectMenu( context.translate("commands.error.user", null) } - respondText(context, errorMessage) + respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { - respondText(context, context.translate("commands.error.user", null)) + respondText( + context, + context.translate("commands.error.user", null), + FailureReason.ExecutionError(t) + ) } } /** Override this to implement a way to respond to the user, regardless of whatever happens. **/ - public abstract suspend fun respondText(context: C, message: String) + public abstract suspend fun respondText(context: C, message: String, failureType: FailureReason<*>) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/FailureReason.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/FailureReason.kt new file mode 100644 index 0000000000..2e300335e4 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/FailureReason.kt @@ -0,0 +1,38 @@ +package com.kotlindiscord.kord.extensions.types + +import com.kotlindiscord.kord.extensions.ArgumentParsingException +import com.kotlindiscord.kord.extensions.DiscordRelayedException + +/** + * Sealed class representing the reason you're dealing with a failure message right now. + * + * If you need to extract the nested throwable, it's recommended that you cast to the required sealed type first to + * make exception typing easier. + * + * @param error Throwable that triggered this failure, if any. + */ +public sealed class FailureReason(public val error: E) { + /** Sealed class representing a basic check failure. **/ + public sealed class BaseCheckFailure(error: E) : + FailureReason(error) + + /** Type representing an error thrown during command/component execution. **/ + public class ExecutionError(error: Throwable) : + FailureReason(error) + + /** Type representing a relayed exception that was thrown during command execution. **/ + public class RelayedFailure(error: DiscordRelayedException) : + FailureReason(error) + + /** Type representing an argument parsing failure, for command types with arguments. **/ + public class ArgumentParsingFailure(error: ArgumentParsingException) : + FailureReason(error) + + /** Type representing a standard "provided" check failure (provided via `check {}`). **/ + public class ProvidedCheckFailure(error: DiscordRelayedException) : + BaseCheckFailure(error) + + /** Type representing a failure caused by the bot having insufficient permissions. **/ + public class OwnPermissionsCheckFailure(error: DiscordRelayedException) : + BaseCheckFailure(error) +} diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt index 43cb939517..95c89d5d83 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeMessageCommand.kt @@ -10,6 +10,7 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeMessageCo import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialMessageCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import com.kotlindiscord.kord.extensions.types.FailureReason import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.respondEphemeral @@ -39,7 +40,9 @@ public class UnsafeMessageCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) @@ -69,19 +72,18 @@ public class UnsafeMessageCommand( try { checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason) - emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, e.reason)) + } catch (t: DiscordRelayedException) { + emitEventAsync(UnsafeMessageCommandFailedChecksEvent(this, event, t.reason)) + respondText(context, t.reason, FailureReason.OwnPermissionsCheckFailure(t)) return } try { - checkBotPerms(context) body(context) } catch (t: Throwable) { if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) } emitEventAsync(UnsafeMessageCommandFailedWithExceptionEvent(this, event, t)) @@ -93,10 +95,19 @@ public class UnsafeMessageCommand( emitEventAsync(UnsafeMessageCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: UnsafeMessageCommandContext, message: String) { + override suspend fun respondText( + context: UnsafeMessageCommandContext, + message: String, + failureType: FailureReason<*> + ) { when (context.interactionResponse) { - is PublicInteractionResponseBehavior -> context.respondPublic { content = message } - is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + is PublicInteractionResponseBehavior -> context.respondPublic { + settings.failureResponseBuilder(this, message, failureType) + } + + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { + settings.failureResponseBuilder(this, message, failureType) + } } } } diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt index b7bcee5a2f..6ec66a4fb7 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeSlashCommand.kt @@ -13,6 +13,7 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeSlashComm import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import com.kotlindiscord.kord.extensions.types.FailureReason import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.respondEphemeral @@ -75,7 +76,9 @@ public class UnsafeSlashCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondPublic { content = e.reason } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) @@ -106,7 +109,7 @@ public class UnsafeSlashCommand( try { checkBotPerms(context) } catch (e: DiscordRelayedException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.OwnPermissionsCheckFailure(e)) emitEventAsync(UnsafeSlashCommandFailedChecksEvent(this, event, e.reason)) return @@ -119,7 +122,7 @@ public class UnsafeSlashCommand( context.populateArgs(args) } } catch (e: ArgumentParsingException) { - respondText(context, e.reason) + respondText(context, e.reason, FailureReason.ArgumentParsingFailure(e)) emitEventAsync(UnsafeSlashCommandFailedParsingEvent(this, event, e)) return @@ -129,7 +132,7 @@ public class UnsafeSlashCommand( body(context) } catch (t: Throwable) { if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) } emitEventAsync(UnsafeSlashCommandFailedWithExceptionEvent(this, event, t)) @@ -141,10 +144,19 @@ public class UnsafeSlashCommand( emitEventAsync(UnsafeSlashCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: UnsafeSlashCommandContext, message: String) { + override suspend fun respondText( + context: UnsafeSlashCommandContext, + message: String, + failureType: FailureReason<*> + ) { when (context.interactionResponse) { - is PublicInteractionResponseBehavior -> context.respondPublic { content = message } - is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + is PublicInteractionResponseBehavior -> context.respondPublic { + settings.failureResponseBuilder(this, message, failureType) + } + + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { + settings.failureResponseBuilder(this, message, failureType) + } } } } diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt index 58b56bc453..218739502e 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/commands/UnsafeUserCommand.kt @@ -10,6 +10,7 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.contexts.UnsafeUserComma import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialUserCommandResponse import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondPublic +import com.kotlindiscord.kord.extensions.types.FailureReason import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.respondEphemeral @@ -40,7 +41,9 @@ public class UnsafeUserCommand( return } } catch (e: DiscordRelayedException) { - event.interaction.respondEphemeral { content = e.reason } + event.interaction.respondEphemeral { + settings.failureResponseBuilder(this, e.reason, FailureReason.ProvidedCheckFailure(e)) + } emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) @@ -70,9 +73,9 @@ public class UnsafeUserCommand( try { checkBotPerms(context) - } catch (e: DiscordRelayedException) { - respondText(context, e.reason) - emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, e.reason)) + } catch (t: DiscordRelayedException) { + emitEventAsync(UnsafeUserCommandFailedChecksEvent(this, event, t.reason)) + respondText(context, t.reason, FailureReason.OwnPermissionsCheckFailure(t)) return } @@ -81,7 +84,7 @@ public class UnsafeUserCommand( body(context) } catch (t: Throwable) { if (t is DiscordRelayedException) { - respondText(context, t.reason) + respondText(context, t.reason, FailureReason.RelayedFailure(t)) } emitEventAsync(UnsafeUserCommandFailedWithExceptionEvent(this, event, t)) @@ -93,10 +96,19 @@ public class UnsafeUserCommand( emitEventAsync(UnsafeUserCommandSucceededEvent(this, event)) } - override suspend fun respondText(context: UnsafeUserCommandContext, message: String) { + override suspend fun respondText( + context: UnsafeUserCommandContext, + message: String, + failureType: FailureReason<*> + ) { when (context.interactionResponse) { - is PublicInteractionResponseBehavior -> context.respondPublic { content = message } - is EphemeralInteractionResponseBehavior -> context.respondEphemeral { content = message } + is PublicInteractionResponseBehavior -> context.respondPublic { + settings.failureResponseBuilder(this, message, failureType) + } + + is EphemeralInteractionResponseBehavior -> context.respondEphemeral { + settings.failureResponseBuilder(this, message, failureType) + } } } } From 82c7580a0e5935ea4ed50c6bfb2e715e63cb4413 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 2 Oct 2021 20:31:11 +0100 Subject: [PATCH 115/131] Member.hasRoles should use role IDs --- .../com/kotlindiscord/kord/extensions/utils/_Member.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt index adf971690e..f3ff592a3e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Member.kt @@ -1,6 +1,7 @@ package com.kotlindiscord.kord.extensions.utils import dev.kord.common.entity.Permission +import dev.kord.core.behavior.RoleBehavior import dev.kord.core.entity.Guild import dev.kord.core.entity.Member import dev.kord.core.entity.Role @@ -12,7 +13,7 @@ import kotlinx.coroutines.flow.toList * @param role Role to check for * @return true if the user has the given role, false otherwise */ -public suspend fun Member.hasRole(role: Role): Boolean = roles.toList().contains(role) +public fun Member.hasRole(role: RoleBehavior): Boolean = roleIds.contains(role.id) /** * Check if the user has all of the given roles. @@ -20,7 +21,7 @@ public suspend fun Member.hasRole(role: Role): Boolean = roles.toList().contains * @param roles Roles to check for. * @return `true` if the user has all of the given roles, `false` otherwise. */ -public suspend inline fun Member.hasRoles(vararg roles: Role): Boolean = hasRoles(roles.toList()) +public fun Member.hasRoles(vararg roles: RoleBehavior): Boolean = hasRoles(roles.toList()) /** * Check if the user has all of the given roles. @@ -28,11 +29,11 @@ public suspend inline fun Member.hasRoles(vararg roles: Role): Boolean = hasRole * @param roles Roles to check for. * @return `true` if the user has all of the given roles, `false` otherwise. */ -public suspend fun Member.hasRoles(roles: Collection): Boolean = +public fun Member.hasRoles(roles: Collection): Boolean = if (roles.isEmpty()) { true } else { - this.roles.toList().containsAll(roles) + this.roleIds.containsAll(roles.map { it.id }) } /** From 184c32e1359d2c2655f3d55c2d07e8152a9e12f5 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 4 Oct 2021 11:22:43 +0100 Subject: [PATCH 116/131] Attempted fix for application command deletion --- .../commands/application/ApplicationCommandRegistry.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ef95d350f1..48bf62a468 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 @@ -675,7 +675,7 @@ public open class ApplicationCommandRegistry : KoinComponent { command.guildId!!, kord.resources.applicationId, discordCommandId - ) + ).delete() } else { kord.unsafe.globalApplicationCommand(kord.resources.applicationId, discordCommandId).delete() } From e520a46ad781278c0675d57ed705e5463272b44f Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 4 Oct 2021 20:55:44 +0100 Subject: [PATCH 117/131] Switch to official Gradle CI task --- .github/workflows/ci.yml | 4 +++- .github/workflows/publish.yml | 8 ++++++-- .github/workflows/tag.yml | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60c02b50cc..556e83db4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,9 @@ jobs: echo "org.gradle.jvmargs=-XX:MaxMetaspaceSize=5G" >> ~/.gradle/gradle.properties - name: Gradle (Build) - run: sh gradlew build + uses: gradle/gradle-build-action@v2 + with: + arguments: build - name: Upload artifact (Extra Module JARs) uses: actions/upload-artifact@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6fb652cf33..14b94feeae 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,10 +34,14 @@ jobs: echo -e "\nsigning.gnupg.passphrase=${{ secrets.GPG_PASSWORD }}" >> ~/.gradle/gradle.properties - name: Gradle (Build) - run: sh gradlew build + uses: gradle/gradle-build-action@v2 + with: + arguments: build - name: Gradle (Publish) - run: sh gradlew -Pkotdis.user=${{ secrets.MAVEN_USER }} -Pkotdis.password=${{ secrets.MAVEN_PASSWORD }} publish + uses: gradle/gradle-build-action@v2 + with: + arguments: publish -Pkotdis.user=${{ secrets.MAVEN_USER }} -Pkotdis.password=${{ secrets.MAVEN_PASSWORD }} - name: Upload artifact (Extra Module JARs) uses: actions/upload-artifact@v2 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 695382c896..eb3e582b79 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -43,10 +43,14 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' - name: Gradle (Build) - run: sh gradlew build + uses: gradle/gradle-build-action@v2 + with: + arguments: build - name: Gradle (Publish) - run: sh gradlew -Pkotdis.user=${{ secrets.MAVEN_USER }} -Pkotdis.password=${{ secrets.MAVEN_PASSWORD }} publish + uses: gradle/gradle-build-action@v2 + with: + arguments: publish -Pkotdis.user=${{ secrets.MAVEN_USER }} -Pkotdis.password=${{ secrets.MAVEN_PASSWORD }} - name: Create release description run: kotlin .github/tag.main.kts From a36930e18891d49b16ab62f55f7dde6c7e1a79df Mon Sep 17 00:00:00 2001 From: Alex <2230292+ByteAlex@users.noreply.github.com> Date: Tue, 5 Oct 2021 00:18:54 +0200 Subject: [PATCH 118/131] First approach for a generic registry storage interface (#97) --- .../builders/ExtensibleBotBuilder.kt | 3 +- .../application/ApplicationCommandRegistry.kt | 649 +++++------------- .../DefaultApplicationCommandRegistry.kt | 383 +++++++++++ .../StorageAwareApplicationCommandRegistry.kt | 138 ++++ .../components/ComponentContainer.kt | 16 +- .../components/ComponentRegistry.kt | 18 +- .../kord/extensions/components/_Functions.kt | 4 +- .../pagination/BaseButtonPaginator.kt | 6 +- ...uctingApplicationCommandRegistryStorage.kt | 79 +++ .../registry/DefaultLocalRegistryStorage.kt | 34 + .../extensions/registry/RegistryStorage.kt | 65 ++ 11 files changed, 877 insertions(+), 518 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/AbstractDeconstructingApplicationCommandRegistryStorage.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/DefaultLocalRegistryStorage.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/RegistryStorage.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d16c820432..d9bb9297f8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -10,6 +10,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.application.ApplicationCommandRegistry +import com.kotlindiscord.kord.extensions.commands.application.DefaultApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.components.ComponentRegistry import com.kotlindiscord.kord.extensions.extensions.Extension @@ -973,7 +974,7 @@ public open class ExtensibleBotBuilder { /** @suppress Builder that shouldn't be set directly by the user. **/ public var applicationCommandRegistryBuilder: () -> ApplicationCommandRegistry = - { ApplicationCommandRegistry() } + { DefaultApplicationCommandRegistry() } /** * List of message command checks. 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 48bf62a468..ae38bfc5ef 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 @@ -1,8 +1,11 @@ @file:Suppress( + "UNCHECKED_CAST", "TooGenericExceptionCaught", "StringLiteralDuplication", - "AnnotationSpacing", - "SpacingBetweenAnnotations" +) +@file:OptIn( + KordUnsafe::class, + KordExperimental::class ) package com.kotlindiscord.kord.extensions.commands.application @@ -14,28 +17,37 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommand import com.kotlindiscord.kord.extensions.commands.application.user.UserCommand import com.kotlindiscord.kord.extensions.commands.converters.SlashCommandConverter import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe import dev.kord.common.entity.ApplicationCommandType import dev.kord.common.entity.Snowflake import dev.kord.core.Kord -import dev.kord.core.behavior.createApplicationCommands import dev.kord.core.behavior.createChatInputCommand import dev.kord.core.behavior.createMessageCommand import dev.kord.core.behavior.createUserCommand +import dev.kord.core.entity.Guild 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.* -import dev.kord.rest.json.JsonErrorCode import dev.kord.rest.request.KtorRequestException -import kotlinx.coroutines.flow.toList +import mu.KLogger import mu.KotlinLogging import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.* -/** Registry for all Discord application commands. **/ -public open class ApplicationCommandRegistry : KoinComponent { - private val logger = KotlinLogging.logger { } +/** + * Abstract class representing common behavior for application command registries. + * + * Deals with the registration and syncing of, and dispatching to, all application commands. + * Subtypes should build their functionality on top of this type. + * + * @see DefaultApplicationCommandRegistry + */ +public abstract class ApplicationCommandRegistry : KoinComponent { + + protected val logger: KLogger = KotlinLogging.logger { } /** Current instance of the bot. **/ public open val bot: ExtensibleBot by inject() @@ -43,23 +55,14 @@ public open class ApplicationCommandRegistry : KoinComponent { /** Kord instance, backing the ExtensibleBot. **/ public open val kord: Kord by inject() - /** Command parser to use for slash commands. **/ - public open val argumentParser: SlashCommandParser = SlashCommandParser() - /** Translations provider, for retrieving translations. **/ public open val translationsProvider: TranslationsProvider by inject() - /** Mapping of Discord-side command ID to a message command object. **/ - public open val messageCommands: MutableMap> = mutableMapOf() - - /** Mapping of Discord-side command ID to a slash command object. **/ - public open val slashCommands: MutableMap> = mutableMapOf() - - /** Mapping of Discord-side command ID to a user command object. **/ - public open val userCommands: MutableMap> = mutableMapOf() + /** Command parser to use for slash commands. **/ + public val argumentParser: SlashCommandParser = SlashCommandParser() /** Whether the initial sync has been finished, and commands should be registered directly. **/ - public open var initialised: Boolean = false + public var initialised: Boolean = false /** Quick access to the human-readable name for a Discord application command type. **/ public val ApplicationCommandType.name: String @@ -72,17 +75,11 @@ public open class ApplicationCommandRegistry : KoinComponent { } /** Handles the initial registration of commands, after extensions have been loaded. **/ - public open suspend fun initialRegistration() { + public suspend fun initialRegistration() { if (initialised) { return } - if (!bot.settings.applicationCommandsBuilder.register) { - logger.debug { - "Application command registration is disabled, pairing existing commands with extension commands" - } - } - val commands: MutableList> = mutableListOf() bot.extensions.values.forEach { @@ -92,304 +89,109 @@ public open class ApplicationCommandRegistry : KoinComponent { } try { - syncAll(true, commands) + initialize(commands) } catch (t: Throwable) { - logger.error(t) { "Failed to synchronise application commands" } + logger.error(t) { "Failed to initialize registry" } } initialised = true } - // region: Untyped sync functions - - /** Register multiple generic application commands. **/ - public open suspend fun syncAll(removeOthers: Boolean = false, commands: List>) { - val groupedCommands = commands.groupBy { it.guildId } - - groupedCommands.forEach { - try { - sync(removeOthers, it.key, it.value) - } catch (e: KtorRequestException) { - logger.error(e) { - var message = if (it.key == null) { - "Failed to synchronise global application commands" - } else { - "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" - } - - if (e.error?.message != null) { - message += "\n Discord error message: ${e.error?.message}" - } - - if (e.error?.code == JsonErrorCode.MissingAccess) { - message += "\n Double-check that the bot was added to this guild with the " + - "`application.commands` scope enabled" - } - - message - } - } catch (t: Throwable) { - logger.error(t) { - if (it.key == null) { - "Failed to synchronise global application commands" - } else { - "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" - } - } - } - } - - val commandsWithPerms = (messageCommands + slashCommands + userCommands) - .filterValues { - it.allowedRoles.isNotEmpty() || - it.allowedUsers.isNotEmpty() || - it.disallowedRoles.isNotEmpty() || - it.disallowedUsers.isNotEmpty() || - !it.allowByDefault - } - .toList() - .groupBy { it.second.guildId } + /** Called once the initial registration started and all extensions are loaded. **/ + protected abstract suspend fun initialize(commands: List>) - try { - commandsWithPerms.forEach { (guildId, commands) -> - if (guildId != null) { - kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guildId) { - commands.forEach { (id, commandObj) -> - command(id) { - commandObj.allowedUsers.map { user(it, true) } - commandObj.disallowedUsers.map { user(it, false) } - - commandObj.allowedRoles.map { role(it, true) } - commandObj.disallowedRoles.map { role(it, false) } - } - } - } + /** Register a [SlashCommand] to the registry. + * + * This method is only called after the [initialize] method and allows runtime modifications. + */ + public abstract suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *>? - logger.trace { "Applied permissions for ${commands.size} commands." } - } else { - logger.warn { "Applying permissions to global application commands is currently not supported." } - } - } - } catch (e: KtorRequestException) { - logger.error(e) { - "Failed to apply application command permissions - for this reason, all commands with configured" + - "permissions will be disabled." + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - } catch (t: Throwable) { - logger.error(t) { - "Failed to apply application command permissions - for this reason, all commands with configured" + - "permissions will be disabled." - } + /** + * Register a [MessageCommand] to the registry. + * + * This method is only called after the [initialize] method and allows runtime modifications. + */ + public abstract suspend fun register(command: MessageCommand<*>): MessageCommand<*>? - commandsWithPerms.forEach { (_, commands) -> - commands.forEach { (id, _) -> - messageCommands.remove(id) - slashCommands.remove(id) - userCommands.remove(id) - } - } - } - } + /** Register a [UserCommand] to the registry. + * + * This method is only called after the [initialize] method and allows runtime modifications. + */ + public abstract suspend fun register(command: UserCommand<*>): UserCommand<*>? - /** Register multiple generic application commands. **/ - public open suspend fun sync( - removeOthers: Boolean = false, - guildId: Snowflake?, - commands: List> - ) { - // NOTE: Someday, discord will make real i18n possible, we hope... - val locale = bot.settings.i18nBuilder.defaultLocale + /** Event handler for slash commands. **/ + public abstract suspend fun handle(event: ChatInputCommandInteractionCreateEvent) - val guild = if (guildId != null) { - kord.getGuild(guildId) - ?: return logger.debug { - "Cannot register application commands for guild ID ${guildId.asString}, " + - "as it seems to be missing." - } - } else { - null - } + /** Event handler for message commands. **/ + public abstract suspend fun handle(event: MessageCommandInteractionCreateEvent) - // Get guild commands if we're registering them (guild != null), otherwise get global commands - val registered = guild?.commands?.toList() - ?: kord.globalCommands.toList() + /** Event handler for user commands. **/ + public abstract suspend fun handle(event: UserCommandInteractionCreateEvent) - if (!bot.settings.applicationCommandsBuilder.register) { - commands.forEach { commandObj -> - val existingCommand = registered.firstOrNull { commandObj.matches(locale, it) } + /** Unregister a slash command. **/ + public abstract suspend fun unregister(command: SlashCommand<*, *>, delete: Boolean = true): SlashCommand<*, *>? - if (existingCommand != null) { - when (commandObj) { - is MessageCommand<*> -> messageCommands[existingCommand.id] = commandObj - is SlashCommand<*, *> -> slashCommands[existingCommand.id] = commandObj - is UserCommand<*> -> userCommands[existingCommand.id] = commandObj - } - } - } + /** Unregister a message command. **/ + public abstract suspend fun unregister(command: MessageCommand<*>, delete: Boolean = true): MessageCommand<*>? - return // We're only syncing them up, there's no other API work to do - } + /** Unregister a user command. **/ + public abstract suspend fun unregister(command: UserCommand<*>, delete: Boolean = true): UserCommand<*>? - // Extension commands that haven't been registered yet - val toAdd = commands.filter { c -> registered.all { !c.matches(locale, it) } } + // region: Utilities - // Extension commands that were previously registered - val toUpdate = commands.filter { c -> registered.any { c.matches(locale, it) } } + /** Unregister a generic [ApplicationCommand]. **/ + public open suspend fun unregisterGeneric( + command: ApplicationCommand<*>, + delete: Boolean = true, + ): ApplicationCommand<*>? = + when (command) { + is MessageCommand<*> -> unregister(command, delete) + is SlashCommand<*, *> -> unregister(command, delete) + is UserCommand<*> -> unregister(command, delete) - // Registered Discord commands that haven't been provided by extensions - val toRemove = if (removeOthers) { - registered.filter { c -> commands.all { !it.matches(locale, c) } } - } else { - listOf() + else -> error("Unsupported application command type: ${command.type.name}") } - logger.info { - var message = if (guild == null) { - "Global application commands: ${toAdd.size} to add / " + - "${toUpdate.size} to update / " + - "${toRemove.size} to remove" + /** @suppress Internal function used to delete the given command from Discord. Used by [unregister]. **/ + public open suspend fun deleteGeneric( + command: ApplicationCommand<*>, + discordCommandId: Snowflake, + ) { + try { + if (command.guildId != null) { + kord.unsafe.guildApplicationCommand( + command.guildId!!, + kord.resources.applicationId, + discordCommandId + ).delete() } else { - "Application commands for guild ${guild.name}: ${toAdd.size} to add / " + - "${toUpdate.size} to update / " + - "${toRemove.size} to remove" - } - - if (!removeOthers) { - message += "\nThe `removeOthers` parameter is `false`, so no commands will be removed." + kord.unsafe.globalApplicationCommand(kord.resources.applicationId, discordCommandId).delete() } - - message - } - - val toCreate = toAdd + toUpdate - - @Suppress("IfThenToElvis") // Ultimately, this is far more readable - val response = if (guild == null) { - // We're registering global commands here, if the guild is null - - kord.createGlobalApplicationCommands { - toCreate.forEach { - val name = it.getTranslatedName(locale) - - logger.trace { "Adding/updating global ${it.type.name} command: $name" } - - when (it) { - is MessageCommand<*> -> message(name) { this.register(locale, it) } - is UserCommand<*> -> user(name) { this.register(locale, it) } - - is SlashCommand<*, *> -> input( - name, it.getTranslatedDescription(locale) - ) { this.register(locale, it) } - } - } - }.toList() - } else { - // We're registering guild-specific commands here, if the guild is available - - guild.createApplicationCommands { - toCreate.forEach { - val name = it.getTranslatedName(locale) - - logger.trace { "Adding/updating guild-specific ${it.type.name} command: $name" } - - when (it) { - is MessageCommand<*> -> message(name) { this.register(locale, it) } - is UserCommand<*> -> user(name) { this.register(locale, it) } - - is SlashCommand<*, *> -> input( - name, it.getTranslatedDescription(locale) - ) { this.register(locale, it) } + } catch (e: KtorRequestException) { + logger.warn(e) { + "Failed to delete ${command.type.name} command ${command.name}" + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" } - } - }.toList() - } - - // Next, we need to associate all the commands we just registered with the commands in our extensions - toCreate.forEach { command -> - val match = response.first { command.matches(locale, it) } - - when (command) { - is MessageCommand<*> -> messageCommands[match.id] = command - is SlashCommand<*, *> -> slashCommands[match.id] = command - is UserCommand<*> -> userCommands[match.id] = command - } - } - - // Finally, we can remove anything that needs to be removed - toRemove.forEach { - logger.trace { "Removing ${it.type.name} command: ${it.name}" } - it.delete() - } - - logger.info { - if (guild == null) { - "Finished synchronising global application commands" - } else { - "Finished synchronising application commands for guild ${guild.name}" } } } - // endregion - - // region: Typed batch registration functions - - /** Register multiple message commands. **/ - public open suspend fun registerAll(vararg commands: MessageCommand<*>): List> = - commands.map { - try { - register(it) as MessageCommand<*> - } catch (e: KtorRequestException) { - logger.warn(e) { - "Failed to register ${it.type.name} command: ${it.name}" + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - - null - } catch (t: Throwable) { - logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } - - null - } - }.filterNotNull() - /** Register multiple slash commands. **/ - public open suspend fun registerAll(vararg commands: SlashCommand<*, *>): List> = - commands.map { + public open suspend fun > registerAll(vararg commands: T): List = + commands.mapNotNull { try { - register(it) as SlashCommand<*, *> - } catch (e: KtorRequestException) { - logger.warn(e) { - "Failed to register ${it.type.name} command: ${it.name}" + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } + when (it) { + is SlashCommand<*, *> -> register(it) as T + is MessageCommand<*> -> register(it) as T + is UserCommand<*> -> register(it) as T + + else -> throw IllegalArgumentException( + "The registry does not know about this type of ApplicationCommand" + ) } - - null - } catch (t: Throwable) { - logger.warn(t) { "Failed to register ${it.type.name} command: ${it.name}" } - - null - } - }.filterNotNull() - - /** Register multiple user commands. **/ - public open suspend fun registerAll(vararg commands: UserCommand<*>): List> = - commands.map { - try { - register(it) as UserCommand<*> } catch (e: KtorRequestException) { logger.warn(e) { "Failed to register ${it.type.name} command: ${it.name}" + @@ -406,14 +208,23 @@ public open class ApplicationCommandRegistry : KoinComponent { null } - }.filterNotNull() + } - // endregion + /** + * Creates a KordEx [ApplicationCommand] as discord command and returns the created command's id as [Snowflake]. + */ + public open suspend fun createDiscordCommand(command: ApplicationCommand<*>): Snowflake? = when (command) { + is SlashCommand<*, *> -> createDiscordSlashCommand(command) + is UserCommand<*> -> createDiscordUserCommand(command) + is MessageCommand<*> -> createDiscordMessageCommand(command) - // region: Typed registration functions + else -> throw IllegalArgumentException("Unknown ApplicationCommand type") + } - /** Register a message command. **/ - public open suspend fun register(command: MessageCommand<*>): MessageCommand<*>? { + /** + * Creates a KordEx [SlashCommand] as discord command and returns the created command's id as [Snowflake]. + */ + public open suspend fun createDiscordSlashCommand(command: SlashCommand<*, *>): Snowflake? { val locale = bot.settings.i18nBuilder.defaultLocale val guild = if (command.guildId != null) { @@ -423,11 +234,12 @@ public open class ApplicationCommandRegistry : KoinComponent { } val name = command.getTranslatedName(locale) + val description = command.getTranslatedDescription(locale) val response = if (guild == null) { // We're registering global commands here, if the guild is null - kord.createGlobalMessageCommand(name) { + kord.createGlobalChatInputCommand(name, description) { logger.trace { "Adding/updating global ${command.type.name} command: $name" } this.register(locale, command) @@ -435,51 +247,22 @@ public open class ApplicationCommandRegistry : KoinComponent { } else { // We're registering guild-specific commands here, if the guild is available - guild.createMessageCommand(name) { + guild.createChatInputCommand(name, description) { logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } this.register(locale, command) } } - try { - if (guild != null) { - kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { - command.allowedUsers.map { user(it, true) } - command.disallowedUsers.map { user(it, false) } - - command.allowedRoles.map { role(it, true) } - command.disallowedRoles.map { role(it, false) } - } - - logger.trace { "Applied permissions for command: ${command.name} ($command)" } - } else { - logger.warn { "Applying permissions to global application commands is currently not supported." } - } - } catch (e: KtorRequestException) { - logger.error(e) { - "Failed to apply application command permissions. This command will not be registered." + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - } catch (t: Throwable) { - logger.error(t) { - "Failed to apply application command permissions. This command will not be registered." - } - - return null - } - - messageCommands[response.id] = command + injectPermissions(guild, command, response.id) ?: return null - return command + return response.id } - /** Register a slash command. **/ - public open suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *>? { + /** + * Creates a KordEx [UserCommand] as discord command and returns the created command's id as [Snowflake]. + */ + public open suspend fun createDiscordUserCommand(command: UserCommand<*>): Snowflake? { val locale = bot.settings.i18nBuilder.defaultLocale val guild = if (command.guildId != null) { @@ -489,12 +272,11 @@ public open class ApplicationCommandRegistry : KoinComponent { } val name = command.getTranslatedName(locale) - val description = command.getTranslatedDescription(locale) val response = if (guild == null) { // We're registering global commands here, if the guild is null - kord.createGlobalChatInputCommand(name, description) { + kord.createGlobalUserCommand(name) { logger.trace { "Adding/updating global ${command.type.name} command: $name" } this.register(locale, command) @@ -502,51 +284,22 @@ public open class ApplicationCommandRegistry : KoinComponent { } else { // We're registering guild-specific commands here, if the guild is available - guild.createChatInputCommand(name, description) { + guild.createUserCommand(name) { logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } this.register(locale, command) } } - try { - if (guild != null) { - kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { - command.allowedUsers.map { user(it, true) } - command.disallowedUsers.map { user(it, false) } - - command.allowedRoles.map { role(it, true) } - command.disallowedRoles.map { role(it, false) } - } - - logger.trace { "Applied permissions for command: ${command.name} ($command)" } - } else { - logger.warn { "Applying permissions to global application commands is currently not supported." } - } - } catch (e: KtorRequestException) { - logger.error(e) { - "Failed to apply application command permissions. This command will not be registered." + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - } catch (t: Throwable) { - logger.error(t) { - "Failed to apply application command permissions. This command will not be registered." - } - - return null - } - - slashCommands[response.id] = command + injectPermissions(guild, command, response.id) ?: return null - return command + return response.id } - /** Register a user command. **/ - public open suspend fun register(command: UserCommand<*>): UserCommand<*>? { + /** + * Creates a KordEx [MessageCommand] as discord command and returns the created command's id as [Snowflake]. + */ + public open suspend fun createDiscordMessageCommand(command: MessageCommand<*>): Snowflake? { val locale = bot.settings.i18nBuilder.defaultLocale val guild = if (command.guildId != null) { @@ -560,7 +313,7 @@ public open class ApplicationCommandRegistry : KoinComponent { val response = if (guild == null) { // We're registering global commands here, if the guild is null - kord.createGlobalUserCommand(name) { + kord.createGlobalMessageCommand(name) { logger.trace { "Adding/updating global ${command.type.name} command: $name" } this.register(locale, command) @@ -568,21 +321,31 @@ public open class ApplicationCommandRegistry : KoinComponent { } else { // We're registering guild-specific commands here, if the guild is available - guild.createUserCommand(name) { + guild.createMessageCommand(name) { logger.trace { "Adding/updating guild-specific ${command.type.name} command: $name" } this.register(locale, command) } } + injectPermissions(guild, command, response.id) ?: return null + + return response.id + } + + // endregion + + // region: Permissions + + protected suspend fun > injectPermissions( + guild: Guild?, + command: T, + commandId: Snowflake + ): T? { try { if (guild != null) { - kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, response.id) { - command.allowedUsers.map { user(it, true) } - command.disallowedUsers.map { user(it, false) } - - command.allowedRoles.map { role(it, true) } - command.disallowedRoles.map { role(it, false) } + kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, commandId) { + injectRawPermissions(this, command) } logger.trace { "Applied permissions for command: ${command.name} ($command)" } @@ -605,124 +368,18 @@ public open class ApplicationCommandRegistry : KoinComponent { return null } - - userCommands[response.id] = command - return command } - // endregion - - // region: Unregistration functions - - /** Unregister a message command. **/ - public open suspend fun unregisterGeneric( - command: ApplicationCommand<*>, - delete: Boolean = true - ): ApplicationCommand<*>? = - when (command) { - is MessageCommand<*> -> unregister(command, delete) - is SlashCommand<*, *> -> unregister(command, delete) - is UserCommand<*> -> unregister(command, delete) - - else -> error("Unsupported application command type: ${command.type.name}") - } - - /** Unregister a message command. **/ - public open suspend fun unregister(command: MessageCommand<*>, delete: Boolean = true): MessageCommand<*>? { - val filtered = messageCommands.filter { it.value == command } - val id = filtered.keys.firstOrNull() ?: return null - - if (delete) { - deleteGeneric(command, id) - } - - return messageCommands.remove(id) - } - - /** Unregister a slash command. **/ - public open suspend fun unregister(command: SlashCommand<*, *>, delete: Boolean = true): SlashCommand<*, *>? { - val filtered = slashCommands.filter { it.value == command } - val id = filtered.keys.firstOrNull() ?: return null - - if (delete) { - deleteGeneric(command, id) - } - - return slashCommands.remove(id) - } - - /** Unregister a user command. **/ - public open suspend fun unregister(command: UserCommand<*>, delete: Boolean = true): UserCommand<*>? { - val filtered = userCommands.filter { it.value == command } - val id = filtered.keys.firstOrNull() ?: return null - - if (delete) { - deleteGeneric(command, id) - } - - return userCommands.remove(id) - } - - /** @suppress Internal function used to delete the given command from Discord. Used by [unregister]. **/ - public open suspend fun deleteGeneric( - command: ApplicationCommand<*>, - discordCommandId: Snowflake, + protected fun injectRawPermissions( + builder: ApplicationCommandPermissionsModifyBuilder, + command: ApplicationCommand<*> ) { - try { - if (command.guildId != null) { - kord.unsafe.guildApplicationCommand( - command.guildId!!, - kord.resources.applicationId, - discordCommandId - ).delete() - } else { - kord.unsafe.globalApplicationCommand(kord.resources.applicationId, discordCommandId).delete() - } - } catch (e: KtorRequestException) { - logger.warn(e) { - "Failed to delete ${command.type.name} command ${command.name}" + - if (e.error?.message != null) { - "\n Discord error message: ${e.error?.message}" - } else { - "" - } - } - } - } - - // endregion - - // region: Event handlers - - /** Event handler for message commands. **/ - public open suspend fun handle(event: MessageCommandInteractionCreateEvent) { - val commandId = event.interaction.invokedCommandId - val command = messageCommands[commandId] - - command ?: return logger.warn { "Received interaction for unknown message command: ${commandId.asString}" } - - command.call(event) - } - - /** Event handler for slash commands. **/ - public open suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { - val commandId = event.interaction.command.rootId - val command = slashCommands[commandId] - - command ?: return logger.warn { "Received interaction for unknown slash command: ${commandId.asString}" } - - command.call(event) - } - - /** Event handler for user commands. **/ - public open suspend fun handle(event: UserCommandInteractionCreateEvent) { - val commandId = event.interaction.invokedCommandId - val command = userCommands[commandId] - - command ?: return logger.warn { "Received interaction for unknown user command: ${commandId.asString}" } + command.allowedUsers.map { builder.user(it, true) } + command.disallowedUsers.map { builder.user(it, false) } - command.call(event) + command.allowedRoles.map { builder.role(it, true) } + command.disallowedRoles.map { builder.role(it, false) } } // endregion 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 new file mode 100644 index 0000000000..3d2a4117bf --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/DefaultApplicationCommandRegistry.kt @@ -0,0 +1,383 @@ +@file:Suppress( + "TooGenericExceptionCaught", + "StringLiteralDuplication", + "AnnotationSpacing", + "SpacingBetweenAnnotations" +) + +package com.kotlindiscord.kord.extensions.commands.application + +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 +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.createApplicationCommands +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.json.JsonErrorCode +import dev.kord.rest.request.KtorRequestException +import kotlinx.coroutines.flow.toList + +/** Registry for all Discord application commands. **/ +public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry() { + + /** Mapping of Discord-side command ID to a message command object. **/ + public open val messageCommands: MutableMap> = mutableMapOf() + + /** Mapping of Discord-side command ID to a slash command object. **/ + public open val slashCommands: MutableMap> = mutableMapOf() + + /** Mapping of Discord-side command ID to a user command object. **/ + public open val userCommands: MutableMap> = mutableMapOf() + + public override suspend fun initialize(commands: List>) { + if (!bot.settings.applicationCommandsBuilder.register) { + logger.debug { + "Application command registration is disabled, pairing existing commands with extension commands" + } + } + + try { + syncAll(true, commands) + } catch (t: Throwable) { + logger.error(t) { "Failed to synchronise application commands" } + } + } + + // region: Untyped sync functions + + /** Register multiple generic application commands. **/ + public open suspend fun syncAll(removeOthers: Boolean = false, commands: List>) { + val groupedCommands = commands.groupBy { it.guildId } + + groupedCommands.forEach { + try { + sync(removeOthers, it.key, it.value) + } catch (e: KtorRequestException) { + logger.error(e) { + var message = if (it.key == null) { + "Failed to synchronise global application commands" + } else { + "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" + } + + if (e.error?.message != null) { + message += "\n Discord error message: ${e.error?.message}" + } + + if (e.error?.code == JsonErrorCode.MissingAccess) { + message += "\n Double-check that the bot was added to this guild with the " + + "`application.commands` scope enabled" + } + + message + } + } catch (t: Throwable) { + logger.error(t) { + if (it.key == null) { + "Failed to synchronise global application commands" + } else { + "Failed to synchronise application commands for guild with ID: ${it.key!!.asString}" + } + } + } + } + + val commandsWithPerms = (messageCommands + slashCommands + userCommands) + .filterValues { + it.allowedRoles.isNotEmpty() || + it.allowedUsers.isNotEmpty() || + it.disallowedRoles.isNotEmpty() || + it.disallowedUsers.isNotEmpty() || + !it.allowByDefault + } + .toList() + .groupBy { it.second.guildId } + + try { + commandsWithPerms.forEach { (guildId, commands) -> + if (guildId != null) { + kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guildId) { + commands.forEach { (id, commandObj) -> + command(id) { injectRawPermissions(this, commandObj) } + } + } + + logger.trace { "Applied permissions for ${commands.size} commands." } + } else { + logger.warn { "Applying permissions to global application commands is currently not supported." } + } + } + } catch (e: KtorRequestException) { + logger.error(e) { + "Failed to apply application command permissions - for this reason, all commands with configured" + + "permissions will be disabled." + + if (e.error?.message != null) { + "\n Discord error message: ${e.error?.message}" + } else { + "" + } + } + } catch (t: Throwable) { + logger.error(t) { + "Failed to apply application command permissions - for this reason, all commands with configured" + + "permissions will be disabled." + } + + commandsWithPerms.forEach { (_, commands) -> + commands.forEach { (id, _) -> + messageCommands.remove(id) + slashCommands.remove(id) + userCommands.remove(id) + } + } + } + } + + /** Register multiple generic application commands. **/ + public open suspend fun sync( + removeOthers: Boolean = false, + guildId: Snowflake?, + commands: List> + ) { + // NOTE: Someday, discord will make real i18n possible, we hope... + val locale = bot.settings.i18nBuilder.defaultLocale + + val guild = if (guildId != null) { + kord.getGuild(guildId) + ?: return logger.debug { + "Cannot register application commands for guild ID ${guildId.asString}, " + + "as it seems to be missing." + } + } else { + null + } + + // Get guild commands if we're registering them (guild != null), otherwise get global commands + val registered = guild?.commands?.toList() + ?: kord.globalCommands.toList() + + if (!bot.settings.applicationCommandsBuilder.register) { + commands.forEach { commandObj -> + val existingCommand = registered.firstOrNull { commandObj.matches(locale, it) } + + if (existingCommand != null) { + when (commandObj) { + is MessageCommand<*> -> messageCommands[existingCommand.id] = commandObj + is SlashCommand<*, *> -> slashCommands[existingCommand.id] = commandObj + is UserCommand<*> -> userCommands[existingCommand.id] = commandObj + } + } + } + + return // We're only syncing them up, there's no other API work to do + } + + // Extension commands that haven't been registered yet + val toAdd = commands.filter { c -> registered.all { !c.matches(locale, it) } } + + // Extension commands that were previously registered + val toUpdate = commands.filter { c -> registered.any { c.matches(locale, it) } } + + // Registered Discord commands that haven't been provided by extensions + val toRemove = if (removeOthers) { + registered.filter { c -> commands.all { !it.matches(locale, c) } } + } else { + listOf() + } + + logger.info { + var message = if (guild == null) { + "Global application commands: ${toAdd.size} to add / " + + "${toUpdate.size} to update / " + + "${toRemove.size} to remove" + } else { + "Application commands for guild ${guild.name}: ${toAdd.size} to add / " + + "${toUpdate.size} to update / " + + "${toRemove.size} to remove" + } + + if (!removeOthers) { + message += "\nThe `removeOthers` parameter is `false`, so no commands will be removed." + } + + message + } + + val toCreate = toAdd + toUpdate + + @Suppress("IfThenToElvis") // Ultimately, this is far more readable + val response = if (guild == null) { + // We're registering global commands here, if the guild is null + + kord.createGlobalApplicationCommands { + toCreate.forEach { + val name = it.getTranslatedName(locale) + + logger.trace { "Adding/updating global ${it.type.name} command: $name" } + + when (it) { + is MessageCommand<*> -> message(name) { this.register(locale, it) } + is UserCommand<*> -> user(name) { this.register(locale, it) } + + is SlashCommand<*, *> -> input( + name, it.getTranslatedDescription(locale) + ) { this.register(locale, it) } + } + } + }.toList() + } else { + // We're registering guild-specific commands here, if the guild is available + + guild.createApplicationCommands { + toCreate.forEach { + val name = it.getTranslatedName(locale) + + logger.trace { "Adding/updating guild-specific ${it.type.name} command: $name" } + + when (it) { + is MessageCommand<*> -> message(name) { this.register(locale, it) } + is UserCommand<*> -> user(name) { this.register(locale, it) } + + is SlashCommand<*, *> -> input( + name, it.getTranslatedDescription(locale) + ) { this.register(locale, it) } + } + } + }.toList() + } + + // Next, we need to associate all the commands we just registered with the commands in our extensions + toCreate.forEach { command -> + val match = response.first { command.matches(locale, it) } + + when (command) { + is MessageCommand<*> -> messageCommands[match.id] = command + is SlashCommand<*, *> -> slashCommands[match.id] = command + is UserCommand<*> -> userCommands[match.id] = command + } + } + + // Finally, we can remove anything that needs to be removed + toRemove.forEach { + logger.trace { "Removing ${it.type.name} command: ${it.name}" } + it.delete() + } + + logger.info { + if (guild == null) { + "Finished synchronising global application commands" + } else { + "Finished synchronising application commands for guild ${guild.name}" + } + } + } + + // endregion + + // region: Typed registration functions + + /** Register a message command. **/ + public override suspend fun register(command: MessageCommand<*>): MessageCommand<*>? { + val commandId = createDiscordCommand(command) ?: return null + + messageCommands[commandId] = command + + return command + } + + /** Register a slash command. **/ + public override suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *>? { + val commandId = createDiscordCommand(command) ?: return null + + slashCommands[commandId] = command + + return command + } + + /** Register a user command. **/ + public override suspend fun register(command: UserCommand<*>): UserCommand<*>? { + val commandId = createDiscordCommand(command) ?: return null + + userCommands[commandId] = command + + return command + } + + // endregion + + // region: Unregistration functions + + /** Unregister a message command. **/ + public override suspend fun unregister(command: MessageCommand<*>, delete: Boolean): MessageCommand<*>? { + val filtered = messageCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + if (delete) { + deleteGeneric(command, id) + } + + return messageCommands.remove(id) + } + + /** Unregister a slash command. **/ + public override suspend fun unregister(command: SlashCommand<*, *>, delete: Boolean): SlashCommand<*, *>? { + val filtered = slashCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + if (delete) { + deleteGeneric(command, id) + } + + return slashCommands.remove(id) + } + + /** Unregister a user command. **/ + public override suspend fun unregister(command: UserCommand<*>, delete: Boolean): UserCommand<*>? { + val filtered = userCommands.filter { it.value == command } + val id = filtered.keys.firstOrNull() ?: return null + + if (delete) { + deleteGeneric(command, id) + } + + return userCommands.remove(id) + } + + // endregion + + // region: Event handlers + + /** Event handler for message commands. **/ + public override suspend fun handle(event: MessageCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = messageCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown message command: ${commandId.asString}" } + + command.call(event) + } + + /** Event handler for slash commands. **/ + public override suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { + val commandId = event.interaction.command.rootId + val command = slashCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown slash command: ${commandId.asString}" } + + command.call(event) + } + + /** Event handler for user commands. **/ + public override suspend fun handle(event: UserCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = userCommands[commandId] + + command ?: return logger.warn { "Received interaction for unknown user command: ${commandId.asString}" } + + command.call(event) + } + + // endregion +} 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 new file mode 100644 index 0000000000..62052996b5 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/StorageAwareApplicationCommandRegistry.kt @@ -0,0 +1,138 @@ +@file:Suppress( + "UNCHECKED_CAST" +) +@file:OptIn( + ExperimentalCoroutinesApi::class +) + +package com.kotlindiscord.kord.extensions.commands.application + +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 +import com.kotlindiscord.kord.extensions.registry.RegistryStorage +import dev.kord.common.entity.Snowflake +import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent +import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.toList + +/** + * [ApplicationCommandRegistry] which acts based off a specified storage interface. + * + * Discord lifecycles may not be implemented in this class and require manual updating. + */ +public open class StorageAwareApplicationCommandRegistry( + builder: () -> RegistryStorage> +) : ApplicationCommandRegistry() { + + protected open val commandRegistry: RegistryStorage> = builder.invoke() + + override suspend fun initialize(commands: List>) { + commands.forEach { commandRegistry.register(it) } + + val registeredCommands = commandRegistry.entryFlow().toList() + + commands.forEach { command -> + if (registeredCommands.none { it.hasCommand(command) }) { + val commandId = createDiscordCommand(command) + + commandId?.let { + commandRegistry.set(it, command) + } + } + } + } + + override suspend fun register(command: SlashCommand<*, *>): SlashCommand<*, *>? { + val commandId = createDiscordCommand(command) ?: return null + + commandRegistry.set(commandId, command) + + return command + } + + override suspend fun register(command: MessageCommand<*>): MessageCommand<*>? { + val commandId = createDiscordCommand(command) ?: return null + + commandRegistry.set(commandId, command) + + return command + } + + override suspend fun register(command: UserCommand<*>): UserCommand<*>? { + val commandId = createDiscordCommand(command) ?: return null + + commandRegistry.set(commandId, command) + + return command + } + + override suspend fun handle(event: ChatInputCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = commandRegistry.get(commandId) as? SlashCommand<*, *> + + command ?: return logger.warn { "Received interaction for unknown slash command: ${commandId.asString}" } + + command.call(event) + } + + override suspend fun handle(event: MessageCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = commandRegistry.get(commandId) as? MessageCommand<*> + + command ?: return logger.warn { "Received interaction for unknown message command: ${commandId.asString}" } + + command.call(event) + } + + override suspend fun handle(event: UserCommandInteractionCreateEvent) { + val commandId = event.interaction.invokedCommandId + val command = commandRegistry.get(commandId) as? UserCommand<*> + + command ?: return logger.warn { "Received interaction for unknown user command: ${commandId.asString}" } + + command.call(event) + } + + override suspend fun unregister(command: SlashCommand<*, *>, delete: Boolean): SlashCommand<*, *>? = + unregisterApplicationCommand(command, delete) as? SlashCommand<*, *> + + override suspend fun unregister(command: MessageCommand<*>, delete: Boolean): MessageCommand<*>? = + unregisterApplicationCommand(command, delete) as? MessageCommand<*> + + override suspend fun unregister(command: UserCommand<*>, delete: Boolean): UserCommand<*>? = + unregisterApplicationCommand(command, delete) as? UserCommand<*> + + protected open suspend fun unregisterApplicationCommand( + command: ApplicationCommand<*>, + delete: Boolean + ): ApplicationCommand<*>? { + val id = commandRegistry.constructUniqueIdentifier(command) + + val snowflake = commandRegistry.entryFlow() + .firstOrNull { commandRegistry.constructUniqueIdentifier(it.value) == id } + ?.key + + snowflake?.let { + if (delete) { + deleteGeneric(command, it) + } + + return commandRegistry.remove(it) + } + + return null + } + + protected open fun RegistryStorage.StorageEntry>.hasCommand( + command: ApplicationCommand<*> + ): Boolean { + val key = commandRegistry.constructUniqueIdentifier(value) + val other = commandRegistry.constructUniqueIdentifier(command) + + return key == other + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt index 9099076bb1..2d8035fce7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentContainer.kt @@ -71,7 +71,7 @@ public open class ComponentContainer( } /** Remove all components, and unregister them from the [ComponentRegistry]. **/ - public open fun removeAll() { + public open suspend fun removeAll() { rows.toList().flatten().forEach { component -> if (component is ComponentWithID) { registry.unregister(component) @@ -82,7 +82,7 @@ public open class ComponentContainer( } /** Remove the given component, and unregister it from the [ComponentRegistry]. **/ - public open fun remove(component: Component): Boolean { + public open suspend fun remove(component: Component): Boolean { if (rows.any { it.remove(component) }) { if (component is ComponentWithID) { registry.unregister(component) @@ -95,7 +95,7 @@ public open class ComponentContainer( } /** Given two components, replace the old component with the new one and likewise handle registration. **/ - public open fun replace(old: Component, new: Component): Boolean { + public open suspend fun replace(old: Component, new: Component): Boolean { for (row in rows) { val index = row.indexOf(old) @@ -129,7 +129,7 @@ public open class ComponentContainer( * Given an old component ID and new component, replace the old component with the new one and likewise handle * registration. */ - public open fun replace(id: String, new: Component): Boolean { + public open suspend fun replace(id: String, new: Component): Boolean { for (row in rows) { val index = row.indexOfFirst { it is ComponentWithID && it.id == id } @@ -163,7 +163,7 @@ public open class ComponentContainer( * Add a component. New components will be unsorted, or placed in the numbered row denoted by [rowNum] if * possible. */ - public open fun add(component: Component, rowNum: Int? = null) { + public open suspend fun add(component: Component, rowNum: Int? = null) { component.validate() if (rowNum == null) { @@ -199,7 +199,7 @@ public open class ComponentContainer( } /** Sort all components in [unsortedComponents] by packing them into rows as tightly as possible. **/ - public open fun sort() { + public open suspend fun sort() { while (unsortedComponents.isNotEmpty()) { val component = unsortedComponents.removeFirst() var sorted = false @@ -230,7 +230,7 @@ public open class ComponentContainer( } /** Apply the components in this container to a message that's being created. **/ - public open fun MessageCreateBuilder.applyToMessage() { + public open suspend fun MessageCreateBuilder.applyToMessage() { sort() for (row in rows.filter { it.isNotEmpty() }) { @@ -241,7 +241,7 @@ public open class ComponentContainer( } /** Apply the components in this container to a message that's being edited. **/ - public open fun MessageModifyBuilder.applyToMessage() { + public open suspend fun MessageModifyBuilder.applyToMessage() { this.components = mutableListOf() // Clear 'em sort() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt index e4db8e45ea..58fc787325 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentRegistry.kt @@ -2,6 +2,8 @@ package com.kotlindiscord.kord.extensions.components import com.kotlindiscord.kord.extensions.components.buttons.InteractionButtonWithAction import com.kotlindiscord.kord.extensions.components.menus.SelectMenu +import com.kotlindiscord.kord.extensions.registry.DefaultLocalRegistryStorage +import com.kotlindiscord.kord.extensions.registry.RegistryStorage import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent @@ -19,28 +21,28 @@ public open class ComponentRegistry { public open val scheduler: Scheduler = Scheduler() /** Map of registered component IDs to their components. **/ - public open val components: MutableMap = mutableMapOf() + public open val storage: RegistryStorage = DefaultLocalRegistryStorage() /** Register a component. Only components with IDs need registering. **/ - public open fun register(component: ComponentWithID) { + public open suspend fun register(component: ComponentWithID) { logger.trace { "Registering component with ID: ${component.id}" } - components[component.id] = component + storage.set(component.id, component) } /** Unregister a registered component. **/ - public open fun unregister(component: ComponentWithID): Component? = + public open suspend fun unregister(component: ComponentWithID): Component? = unregister(component.id) /** Unregister a registered component, by ID. **/ - public open fun unregister(id: String): Component? = - components.remove(id) + public open suspend fun unregister(id: String): Component? = + storage.remove(id) /** Dispatch a [ButtonInteractionCreateEvent] to its button component object. **/ public suspend fun handle(event: ButtonInteractionCreateEvent) { val id = event.interaction.componentId - when (val c = components[id]) { + when (val c = storage.get(id)) { is InteractionButtonWithAction<*> -> c.call(event) null -> logger.debug { "Button interaction received for unknown component ID: $id" } @@ -56,7 +58,7 @@ public open class ComponentRegistry { public suspend fun handle(event: SelectMenuInteractionCreateEvent) { val id = event.interaction.componentId - when (val c = components[id]) { + when (val c = storage.get(id)) { is SelectMenu<*> -> c.call(event) null -> logger.debug { "Select Menu interaction received for unknown component ID: $id" } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt index 8ea5fb5db2..363ce6ffc2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/_Functions.kt @@ -92,14 +92,14 @@ public suspend fun ComponentContainer.publicSelectMenu( } /** Convenience function for applying the components in a [ComponentContainer] to a message you're creating. **/ -public fun MessageCreateBuilder.applyComponents(components: ComponentContainer) { +public suspend fun MessageCreateBuilder.applyComponents(components: ComponentContainer) { with(components) { applyToMessage() } } /** Convenience function for applying the components in a [ComponentContainer] to a message you're editing. **/ -public fun MessageModifyBuilder.applyComponents(components: ComponentContainer) { +public suspend fun MessageModifyBuilder.applyComponents(components: ComponentContainer) { with(components) { applyToMessage() } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt index c0b1f4f8ad..fb2fcaae85 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/BaseButtonPaginator.kt @@ -275,7 +275,7 @@ public abstract class BaseButtonPaginator( /** * Convenience function that enables and disables buttons as necessary, depending on the current page number. */ - public fun updateButtons() { + public suspend fun updateButtons() { if (currentPageNum <= 0) { setDisabledButton(firstPageButton) setDisabledButton(backButton) @@ -312,7 +312,7 @@ public abstract class BaseButtonPaginator( } /** Replace an enabled interactive button in [components] with a disabled button of the same ID. **/ - public fun setDisabledButton(oldButton: PublicInteractionButton?): Boolean { + public suspend fun setDisabledButton(oldButton: PublicInteractionButton?): Boolean { oldButton ?: return false val newButton = DisabledInteractionButton() @@ -327,7 +327,7 @@ public abstract class BaseButtonPaginator( } /** Replace a disabled button in [components] with the given interactive button of the same ID.. **/ - public fun setEnabledButton(newButton: PublicInteractionButton?): Boolean { + public suspend fun setEnabledButton(newButton: PublicInteractionButton?): Boolean { newButton ?: return false return components.replace(newButton.id, newButton) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/AbstractDeconstructingApplicationCommandRegistryStorage.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/AbstractDeconstructingApplicationCommandRegistryStorage.kt new file mode 100644 index 0000000000..73c01d3602 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/AbstractDeconstructingApplicationCommandRegistryStorage.kt @@ -0,0 +1,79 @@ +package com.kotlindiscord.kord.extensions.registry + +import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand +import dev.kord.common.entity.Snowflake +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull + +/** + * Abstract class which can be used to implement a simple networked key-value based registry storage + * for [ApplicationCommand]s. + * + * For simplicity the parameter / return types of the abstract methods are all [String]s. + */ +public abstract class AbstractDeconstructingApplicationCommandRegistryStorage> : + RegistryStorage { + + /** + * Mapping of command-key to command-object. + */ + protected open val commandMapping: MutableMap = mutableMapOf() + + /** + * Upserts simplified data. + * The key is the command id, which is returned by the create request from discord. + * The value is the command name, which must be unique across the registry. + */ + protected abstract suspend fun upsert(key: String, value: String) + + /** + * Reads simplified data from the storage. + * + * The key is the command id. + * + * Returns the command name associated with this key. + */ + protected abstract suspend fun read(key: String): String? + + /** + * Deletes and returns simplified data. + * + * The key is the command id. + * + * Returns the command name associated with this key. + */ + protected abstract suspend fun delete(key: String): String? + + /** + * Returns all entries in this registry as simplified data. + * + * The key is the command id. + * The value is the command name associated with this key. + */ + protected abstract fun entries(): Flow> + + override fun constructUniqueIdentifier(data: T): String = "${data.name}-${data.type.value}-${data.guildId ?: 0}" + + override suspend fun register(data: T) { + commandMapping[constructUniqueIdentifier(data)] = data + } + + override suspend fun set(id: Snowflake, data: T) { + val key = constructUniqueIdentifier(data) + commandMapping[key] = data + upsert(id.asString, key) + } + + override suspend fun get(id: Snowflake): T? { + val key = read(id.asString) ?: return null + return commandMapping[key] + } + + override suspend fun remove(id: Snowflake): T? { + val key = delete(id.asString) ?: return null + return commandMapping[key] + } + + override fun entryFlow(): Flow> = entries() + .mapNotNull { commandMapping[it.value]?.let { cmd -> RegistryStorage.StorageEntry(Snowflake(it.key), cmd) } } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/DefaultLocalRegistryStorage.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/DefaultLocalRegistryStorage.kt new file mode 100644 index 0000000000..53a9e9a977 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/DefaultLocalRegistryStorage.kt @@ -0,0 +1,34 @@ +package com.kotlindiscord.kord.extensions.registry + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map + +/** + * Default "local" implementation of [RegistryStorage] which internally + * uses a mutableMap. + */ +public open class DefaultLocalRegistryStorage : RegistryStorage { + + protected open val registry: MutableMap = mutableMapOf() + + override suspend fun register(data: T) { + // we don't need to do anything here + } + + override suspend fun set(id: K, data: T) { + registry[id] = data + } + + override suspend fun get(id: K): T? = registry[id] + + override suspend fun remove(id: K): T? = registry.remove(id) + + override fun entryFlow(): Flow> { + return registry.entries + .asFlow() + .map { RegistryStorage.StorageEntry(it.key, it.value) } + } + + override fun constructUniqueIdentifier(data: T): String = data.hashCode().toString() +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/RegistryStorage.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/RegistryStorage.kt new file mode 100644 index 0000000000..bfa4746b79 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/registry/RegistryStorage.kt @@ -0,0 +1,65 @@ +package com.kotlindiscord.kord.extensions.registry + +import kotlinx.coroutines.flow.Flow + +/** + * Interface for interaction based registries like ComponentRegistry and ApplicationCommandRegistry. + * + * The purpose of this interface is to provide a generic way to store Components/ApplicationCommands + * in a dynamic manner. + */ +public interface RegistryStorage { + + /** + * Lets the registry know about the specified type [T], this may store the object in a local map, + * which is used for reconstructing later. + */ + public suspend fun register(data: T) + + /** + * Creates or updates an existing entry at the given unique key. + * + * This may deconstruct the given data and only persists a partial object. + */ + public suspend fun set(id: K, data: T) + + /** + * Reads a value from the registry at the given key. + * + * This may reconstruct the data from a partial object. + */ + public suspend fun get(id: K): T? + + /** + * Deletes a value from the registry with the given key. + * + * The return value may be a reconstructed object from partial data. + */ + public suspend fun remove(id: K): T? + + /** + * Creates a flow of all entries in this registry. + * + * The objects in this flow may be reconstructed from partial data. + */ + public fun entryFlow(): Flow> + + /** + * Constructs a unique key for the given data. + */ + public fun constructUniqueIdentifier(data: T): String + + /** + * Data class to represent an entry in the [RegistryStorage]. + */ + public data class StorageEntry( + /** + * The key of this entry. + */ + val key: K, + /** + * The value of this entry. + */ + val value: V + ) +} From 721ff0e1ef263eb73442484e46749997d6cc7f6d Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 5 Oct 2021 10:52:53 +0100 Subject: [PATCH 119/131] Unregister globals when there are none registered --- .../application/DefaultApplicationCommandRegistry.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 3d2a4117bf..4734975d1d 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 @@ -49,7 +49,11 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry /** Register multiple generic application commands. **/ public open suspend fun syncAll(removeOthers: Boolean = false, commands: List>) { - val groupedCommands = commands.groupBy { it.guildId } + val groupedCommands = commands.groupBy { it.guildId }.toMutableMap() + + if (removeOthers && !groupedCommands.containsKey(null)) { + groupedCommands[null] = listOf() + } groupedCommands.forEach { try { @@ -175,14 +179,14 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry } // Extension commands that haven't been registered yet - val toAdd = commands.filter { c -> registered.all { !c.matches(locale, it) } } + val toAdd = commands.filter { aC -> registered.all { dC -> !aC.matches(locale, dC) } } // Extension commands that were previously registered - val toUpdate = commands.filter { c -> registered.any { c.matches(locale, it) } } + val toUpdate = commands.filter { aC -> registered.any { dC -> aC.matches(locale, dC) } } // Registered Discord commands that haven't been provided by extensions val toRemove = if (removeOthers) { - registered.filter { c -> commands.all { !it.matches(locale, c) } } + registered.filter { dC -> commands.all { aC -> !aC.matches(locale, dC) } } } else { listOf() } From a243d0f2bea5c81f03055ee6f57940ebc60d15b3 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 5 Oct 2021 13:32:05 +0100 Subject: [PATCH 120/131] Slash args now use resolved entities --- .../converters/MappingsVersionConverter.kt | 32 +++--- .../application/slash/SlashCommandParser.kt | 31 +++--- .../converters/impl/EnumChoiceConverter.kt | 13 +++ .../converters/impl/NumberChoiceConverter.kt | 8 ++ .../converters/impl/StringChoiceConverter.kt | 8 ++ .../CoalescingToDefaultingConverter.kt | 11 ++ .../CoalescingToOptionalConverter.kt | 11 ++ .../converters/SingleToDefaultingConverter.kt | 11 ++ .../converters/SingleToOptionalConverter.kt | 11 ++ .../converters/SlashCommandConverter.kt | 5 + .../converters/impl/BooleanConverter.kt | 8 ++ .../converters/impl/ChannelConverter.kt | 8 ++ .../converters/impl/ColorConverter.kt | 31 ++++++ .../converters/impl/DecimalConverter.kt | 8 ++ .../impl/DurationCoalescingConverter.kt | 37 ++++++- .../converters/impl/DurationConverter.kt | 31 ++++++ .../converters/impl/EmailConverter.kt | 15 +++ .../converters/impl/EmojiConverter.kt | 13 +++ .../commands/converters/impl/EnumConverter.kt | 13 +++ .../converters/impl/GuildConverter.kt | 12 ++ .../commands/converters/impl/IntConverter.kt | 80 ++------------ .../commands/converters/impl/LongConverter.kt | 8 ++ .../converters/impl/MemberConverter.kt | 8 ++ .../converters/impl/MessageConverter.kt | 15 ++- .../impl/RegexCoalescingConverter.kt | 8 ++ .../converters/impl/RegexConverter.kt | 8 ++ .../commands/converters/impl/RoleConverter.kt | 8 ++ .../converters/impl/SnowflakeConverter.kt | 15 +++ .../impl/StringCoalescingConverter.kt | 8 ++ .../converters/impl/StringConverter.kt | 8 ++ .../impl/SupportedLocaleConverter.kt | 11 ++ .../commands/converters/impl/UserConverter.kt | 8 ++ .../extensions/sentry/SentryIdConverter.kt | 15 +++ .../kord/extensions/test/bot/TestExtension.kt | 13 +++ .../java/J8DurationCoalescingConverter.kt | 38 ++++++- .../modules/time/java/J8DurationConverter.kt | 32 ++++++ .../time4j/T4JDurationCoalescingConverter.kt | 26 ++++- .../time/time4j/T4JDurationConverter.kt | 20 ++++ .../unsafe/converters/UnionConverter.kt | 103 ++++++++++++++++++ 39 files changed, 633 insertions(+), 116 deletions(-) diff --git a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt index 21733d58dd..83a6afcba6 100644 --- a/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt +++ b/extra-modules/extra-mappings/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/mappings/converters/MappingsVersionConverter.kt @@ -11,6 +11,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.commands.converters.Validator import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import me.shedaniel.linkie.MappingsContainer @@ -34,20 +35,6 @@ class MappingsVersionConverter( if (arg in namespace.getAllVersions()) { val version = namespace.getProvider(arg).getOrNull() -// if (version == null) { -// throw DiscordRelayedException("Invalid ${namespace.id} version: `$arg`") -// -// val created = namespace.createAndAdd(arg) -// -// if (created != null) { -// this.parsed = created -// } else { -// throw DiscordRelayedException("Invalid ${namespace.id} version: `$arg`") -// } -// } else { -// this.parsed = version -// } - if (version != null) { this.parsed = version @@ -60,6 +47,23 @@ class MappingsVersionConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + val namespace = namespaceGetter.invoke() + + if (optionValue in namespace.getAllVersions()) { + val version = namespace.getProvider(optionValue).getOrNull() + + if (version != null) { + this.parsed = version + + return true + } + } + + throw DiscordRelayedException("Invalid ${namespace.id} version: `$optionValue`") + } } /** Optional mappings version converter; see KordEx bundled functions for more info. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt index 542ad01cfe..e88b98ee29 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/SlashCommandParser.kt @@ -11,7 +11,7 @@ import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.converters.* import dev.kord.common.annotation.KordPreview -import dev.kord.core.entity.KordEntity +import dev.kord.core.entity.interaction.OptionValue import mu.KotlinLogging private val logger = KotlinLogging.logger {} @@ -19,9 +19,6 @@ private val logger = KotlinLogging.logger {} /** * Parser in charge of dealing with the arguments for slash commands. * - * This doesn't do anything special with the rich types provided by Discord, as they're useless in most cases. - * Instead, it transforms them to a string and puts them through the usual parsing machinery. - * * This parser does not support multi converters, as there's no good way to represent them with * Discord's API. Coalescing converters will act like single converters. */ @@ -45,17 +42,15 @@ public open class SlashCommandParser { val command = context.event.interaction.command val values = command.options.mapValues { - val option = it.value.value - - if (option is KordEntity) { - option.id.asString + if (it.value is OptionValue.StringOptionValue) { + OptionValue.StringOptionValue((it.value.value as String).trim()) } else { - option.toString() - }.trim() - } + it.value + } + } as Map> var currentArg: Argument<*>? - var currentValue: String? + var currentValue: OptionValue<*>? @Suppress("LoopWithTooManyJumpStatements") // Listen here u lil shit while (true) { @@ -75,7 +70,7 @@ public open class SlashCommandParser { is SingleConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, currentValue) + converter.parseOption(context, currentValue) } else { false } @@ -128,7 +123,7 @@ public open class SlashCommandParser { is CoalescingConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, listOf(currentValue)) > 0 + converter.parseOption(context, currentValue) } else { false } @@ -181,7 +176,7 @@ public open class SlashCommandParser { is OptionalConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, currentValue) + converter.parseOption(context, currentValue) } else { false } @@ -211,7 +206,7 @@ public open class SlashCommandParser { is OptionalCoalescingConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, listOf(currentValue)) > 0 + converter.parseOption(context, currentValue) } else { false } @@ -241,7 +236,7 @@ public open class SlashCommandParser { is DefaultingConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, currentValue) + converter.parseOption(context, currentValue) } else { false } @@ -271,7 +266,7 @@ public open class SlashCommandParser { is DefaultingCoalescingConverter<*> -> try { val parsed = if (currentValue != null) { - converter.parse(null, context, listOf(currentValue)) > 0 + converter.parseOption(context, currentValue) } else { false } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt index 6a97d3b78a..289c2fca48 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/EnumChoiceConverter.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.commands.application.slash.converters.C import com.kotlindiscord.kord.extensions.commands.converters.* import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -51,6 +52,18 @@ public class EnumChoiceConverter( this@EnumChoiceConverter.choices.forEach { choice(it.key, it.value.name) } } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val stringOption = option as? OptionValue.StringOptionValue ?: return false + + try { + parsed = getter.invoke(stringOption.value) ?: return false + } catch (e: IllegalArgumentException) { + return false + } + + return true + } } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt index d3add1ade8..62d168d3c9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt @@ -19,6 +19,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.IntChoiceBuilder import dev.kord.rest.builder.interaction.OptionsBuilder @@ -68,4 +69,11 @@ class NumberChoiceConverter( this@NumberChoiceConverter.choices.forEach { choice(it.key, it.value) } } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.IntOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt index c7250f267d..516f4e8c84 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/StringChoiceConverter.kt @@ -18,6 +18,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -50,4 +51,11 @@ public class StringChoiceConverter( this@StringChoiceConverter.choices.forEach { choice(it.key, it.value) } } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt index 14f4248e8a..a65b3c994f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToDefaultingConverter.kt @@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder /** @@ -57,4 +58,14 @@ public class CoalescingToDefaultingConverter( return option } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val result = coalescingConverter.parseOption(context, option) + + if (result) { + this.parsed = coalescingConverter.parsed + } + + return result + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt index 07868ebd3c..070f7ff60e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/CoalescingToOptionalConverter.kt @@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder /** @@ -56,4 +57,14 @@ public class CoalescingToOptionalConverter( return option } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val result = coalescingConverter.parseOption(context, option) + + if (result) { + this.parsed = coalescingConverter.parsed + } + + return result + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt index bdf48215aa..f52473b7df 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToDefaultingConverter.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder /** @@ -61,4 +62,14 @@ public class SingleToDefaultingConverter( return option } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val result = singleConverter.parseOption(context, option) + + if (result) { + this.parsed = singleConverter.parsed + } + + return result + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt index 39d261cbfc..8330111a7f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SingleToOptionalConverter.kt @@ -4,6 +4,7 @@ import com.kotlindiscord.kord.extensions.commands.Argument import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder /** @@ -60,4 +61,14 @@ public class SingleToOptionalConverter( return option } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val result = singleConverter.parseOption(context, option) + + if (result) { + this.parsed = singleConverter.parsed + } + + return result + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt index d7461e4ca4..3a029d38f7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/SlashCommandConverter.kt @@ -1,7 +1,9 @@ package com.kotlindiscord.kord.extensions.commands.converters import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.CommandContext import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder /** @@ -15,4 +17,7 @@ public interface SlashCommandConverter { * Only applicable to converter types that make sense for slash commands. */ public suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder + + /** Use the given [option] taken straight from the slash command invocation to fill the converter. **/ + public suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt index e0ba1b6532..5dedb0451e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/BooleanConverter.kt @@ -9,6 +9,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.utils.parseBoolean import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.BooleanBuilder import dev.kord.rest.builder.interaction.OptionsBuilder @@ -40,4 +41,11 @@ public class BooleanConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = BooleanBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.BooleanOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt index 6b8f1229bd..aed2a5456c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ChannelConverter.kt @@ -18,6 +18,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.channel.Channel import dev.kord.core.entity.channel.GuildChannel +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.ChannelBuilder import dev.kord.rest.builder.interaction.OptionsBuilder import kotlinx.coroutines.FlowPreview @@ -113,4 +114,11 @@ public class ChannelConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = ChannelBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.ChannelOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt index 64b7375305..79aa5fb753 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/ColorConverter.kt @@ -19,6 +19,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.ColorParser import dev.kord.common.Color import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -64,4 +65,34 @@ public class ColorConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + when { + optionValue.startsWith("#") -> this.parsed = + Color(optionValue.substring(1).toInt(16)) + + optionValue.startsWith("0x") -> this.parsed = + Color(optionValue.substring(2).toInt(16)) + + optionValue.all { it.isDigit() } -> this.parsed = + Color(optionValue.toInt()) + + else -> this.parsed = + ColorParser.parse(optionValue, context.getLocale()) ?: throw DiscordRelayedException( + context.translate("converters.color.error.unknown", replacements = arrayOf(optionValue)) + ) + } + } catch (e: DiscordRelayedException) { + throw e + } catch (t: Throwable) { + throw DiscordRelayedException( + context.translate("converters.color.error.unknownOrFailed", replacements = arrayOf(optionValue)) + ) + } + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt index 2162fe1f2e..955515d5ea 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DecimalConverter.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.NumberChoiceBuilder import dev.kord.rest.builder.interaction.OptionsBuilder @@ -51,4 +52,11 @@ public class DecimalConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = NumberChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.NumberOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt index ad8b5198cb..cc0d884830 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt @@ -13,6 +13,7 @@ import com.kotlindiscord.kord.extensions.parsers.DurationParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.datetime.* @@ -139,9 +140,6 @@ public class DurationCoalescingConverter( return durations.size } - override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = - StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } - private suspend fun throwIfNecessary( e: Exception, context: CommandContext, @@ -164,4 +162,37 @@ public class DurationCoalescingConverter( } else { logger.debug(e) { "Error thrown during duration parsing" } } + + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = + StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + val result: DateTimePeriod = DurationParser.parse(optionValue, context.getLocale()) + + if (positiveOnly) { + val now: Instant = Clock.System.now() + val applied: Instant = now.plus(result, TimeZone.UTC) + + if (now > applied) { + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) + } + } + + parsed = result + } catch (e: InvalidTimeUnitException) { + val message: String = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt index d785527620..259683f261 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt @@ -12,6 +12,7 @@ import com.kotlindiscord.kord.extensions.parsers.DurationParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.datetime.* @@ -73,4 +74,34 @@ public class DurationConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + val result: DateTimePeriod = DurationParser.parse(optionValue, context.getLocale()) + + if (positiveOnly) { + val now: Instant = Clock.System.now() + val applied: Instant = now.plus(result, TimeZone.UTC) + + if (now > applied) { + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) + } + } + + parsed = result + } catch (e: InvalidTimeUnitException) { + val message: String = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt index b8263021f7..584811859c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmailConverter.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import org.apache.commons.validator.routines.EmailValidator @@ -49,4 +50,18 @@ public class EmailConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + if (!EmailValidator.getInstance().isValid(optionValue)) { + throw DiscordRelayedException( + context.translate("converters.email.error.invalid", replacements = arrayOf(optionValue)) + ) + } + + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt index ada3258bb1..d55f7fa873 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EmojiConverter.kt @@ -17,6 +17,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.GuildEmoji +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.coroutines.flow.first @@ -92,4 +93,16 @@ public class EmojiConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + val emoji: GuildEmoji = findEmoji(optionValue, context) + ?: throw DiscordRelayedException( + context.translate("converters.emoji.error.missing", replacements = arrayOf(optionValue)) + ) + + parsed = emoji + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt index f9b3661eb8..db52446b94 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/EnumConverter.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -63,6 +64,18 @@ public class EnumConverter>( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + parsed = getter.invoke(optionValue) ?: return false + } catch (e: IllegalArgumentException) { + return false + } + + return true + } } /** diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt index 9daa34d2cb..6932d9ba13 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/GuildConverter.kt @@ -17,6 +17,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.Guild +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.coroutines.flow.firstOrNull @@ -64,4 +65,15 @@ public class GuildConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + this.parsed = findGuild(optionValue) + ?: throw DiscordRelayedException( + context.translate("converters.guild.error.missing", replacements = arrayOf(optionValue)) + ) + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt index f1fefe663c..84bd340fd7 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.IntChoiceBuilder import dev.kord.rest.builder.interaction.OptionsBuilder @@ -56,76 +57,11 @@ public class IntConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = IntChoiceBuilder(arg.displayName, arg.description).apply { required = true } -} -// /** -// * Create an integer converter, for single arguments. -// * -// * @see IntConverter -// */ -// public fun Arguments.int( -// displayName: String, -// description: String, -// radix: Int = 10, -// validator: Validator = null, -// ): SingleConverter = -// arg(displayName, description, IntConverter(radix, validator)) -// -// /** -// * Create an optional integer converter, for single arguments. -// * -// * @see IntConverter -// */ -// public fun Arguments.optionalInt( -// displayName: String, -// description: String, -// outputError: Boolean = false, -// radix: Int = 10, -// validator: Validator = null, -// ): OptionalConverter = -// arg( -// displayName, -// description, -// IntConverter(radix) -// .toOptional(outputError = outputError, nestedValidator = validator) -// ) -// -// /** -// * Create a defaulting integer converter, for single arguments. -// * -// * @see IntConverter -// */ -// public fun Arguments.defaultingInt( -// displayName: String, -// description: String, -// defaultValue: Int, -// radix: Int = 10, -// validator: Validator = null, -// ): DefaultingConverter = -// arg( -// displayName, -// description, -// IntConverter(radix) -// .toDefaulting(defaultValue, nestedValidator = validator) -// ) -// -// /** -// * Create an integer converter, for lists of arguments. -// * -// * @param required Whether command parsing should fail if no arguments could be converted. -// * -// * @see IntConverter -// */ -// public fun Arguments.intList( -// displayName: String, -// description: String, -// required: Boolean = true, -// radix: Int = 10, -// validator: Validator> = null, -// ): MultiConverter = -// arg( -// displayName, -// description, -// IntConverter(radix) -// .toMulti(required, signatureTypeString = "numbers", nestedValidator = validator) -// ) + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.IntOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt index 481129e386..468e234e1a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/LongConverter.kt @@ -15,6 +15,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.IntChoiceBuilder import dev.kord.rest.builder.interaction.OptionsBuilder @@ -56,4 +57,11 @@ public class LongConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = IntChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.IntOptionValue)?.value ?: return false + this.parsed = optionValue.toLong() + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt index 885d559491..6533a4665a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MemberConverter.kt @@ -20,6 +20,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.Member import dev.kord.core.entity.User +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.UserBuilder import kotlinx.coroutines.flow.firstOrNull @@ -114,4 +115,11 @@ public class MemberConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = UserBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.MemberOptionValue)?.value as? Member ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt index 75adc92412..21db41ca52 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/MessageConverter.kt @@ -23,6 +23,7 @@ import dev.kord.core.entity.channel.DmChannel import dev.kord.core.entity.channel.GuildChannel import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.channel.MessageChannel +import dev.kord.core.entity.interaction.OptionValue import dev.kord.core.exception.EntityNotFoundException import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -189,12 +190,20 @@ public class MessageConverter( } } - override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = - StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } - private suspend fun errorNoMessage(arg: String, context: CommandContext): Nothing { throw DiscordRelayedException( context.translate("converters.message.error.missing", replacements = arrayOf(arg)) ) } + + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = + StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + parsed = findMessage(optionValue, context) + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt index b341658a57..ac45168b70 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexCoalescingConverter.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -54,4 +55,11 @@ public class RegexCoalescingConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = optionValue.toRegex(options) + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt index c4ce6f7779..edafdbe4e0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RegexConverter.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -51,4 +52,11 @@ public class RegexConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = optionValue.toRegex(options) + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt index 46d826d93c..87967e8768 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/RoleConverter.kt @@ -18,6 +18,7 @@ import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.Guild import dev.kord.core.entity.Role +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.RoleBuilder import kotlinx.coroutines.flow.firstOrNull @@ -91,4 +92,11 @@ public class RoleConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = RoleBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.RoleOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt index 99851e70b8..37bba10b19 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SnowflakeConverter.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -52,4 +53,18 @@ public class SnowflakeConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + this.parsed = Snowflake(optionValue) + } catch (e: NumberFormatException) { + throw DiscordRelayedException( + context.translate("converters.snowflake.error.invalid", replacements = arrayOf(optionValue)) + ) + } + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt index 25bab8fd70..3e5fe30742 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringCoalescingConverter.kt @@ -14,6 +14,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -44,4 +45,11 @@ public class StringCoalescingConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt index eddfaad07e..50d8e44284 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/StringConverter.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder @@ -38,4 +39,11 @@ public class StringConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt index a98c53456e..b36218911b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/SupportedLocaleConverter.kt @@ -18,6 +18,7 @@ import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converte import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import java.util.* @@ -54,4 +55,14 @@ public class SupportedLocaleConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + this.parsed = SupportedLocales.ALL_LOCALES[optionValue.lowercase().trim()] ?: throw DiscordRelayedException( + context.translate("converters.supportedLocale.error.unknown", replacements = arrayOf(optionValue)) + ) + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt index bc3838948f..4bf1db2401 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/UserConverter.kt @@ -19,6 +19,7 @@ import com.kotlindiscord.kord.extensions.utils.users import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Snowflake import dev.kord.core.entity.User +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.UserBuilder import kotlinx.coroutines.flow.firstOrNull @@ -100,4 +101,11 @@ public class UserConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = UserBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.UserOptionValue)?.value ?: return false + this.parsed = optionValue + + return true + } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt index 1c6a7e80d9..259c7793b3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/sentry/SentryIdConverter.kt @@ -8,6 +8,7 @@ import com.kotlindiscord.kord.extensions.commands.CommandContext import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import io.sentry.protocol.SentryId @@ -37,4 +38,18 @@ public class SentryIdConverter : SingleConverter() { override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + this.parsed = SentryId(optionValue) + } catch (e: IllegalArgumentException) { + throw DiscordRelayedException( + context.translate("extensions.sentry.converter.error.invalid", replacements = arrayOf(optionValue)) + ) + } + + return true + } } diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index fb78afe3bc..d59eadda7e 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -77,6 +77,10 @@ class TestExtension : Extension() { val message by message("target", "Target message") } + class UserArgs : Arguments() { + val user by user("target", "Target user") + } + override suspend fun setup() { event { action { @@ -84,6 +88,15 @@ class TestExtension : Extension() { } } + publicSlashCommand(::UserArgs) { + name = "slap" + description = "Slap someone!" + + action { + respond { content = "*slaps ${arguments.user.mention}*" } + } + } + publicMessageCommand { name = "Raw Info" diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt index f2418382c9..ac6a4cf0af 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationCoalescingConverter.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import mu.KotlinLogging @@ -137,9 +138,6 @@ public class J8DurationCoalescingConverter( return durations.size } - override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = - StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } - private suspend fun throwIfNecessary( e: Exception, context: CommandContext, @@ -162,6 +160,40 @@ public class J8DurationCoalescingConverter( } else { logger.debug(e) { "Error thrown during duration parsing" } } + + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = + StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val arg = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + val result = J8DurationParser.parse(arg, context.getLocale()) + + if (positiveOnly) { + val normalized = result.clone() + + normalized.normalize(LocalDateTime.now()) + + if (!normalized.isPositive()) { + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) + } + } + + parsed = result + } catch (e: InvalidTimeUnitException) { + val message = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } /** diff --git a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt index e35d3ef8c5..7749bfeba8 100644 --- a/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt +++ b/modules/java-time/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/java/J8DurationConverter.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import java.time.Duration @@ -71,6 +72,37 @@ public class J8DurationConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val arg = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + val result = J8DurationParser.parse(arg, context.getLocale()) + + if (positiveOnly) { + val normalized = result.clone() + + normalized.normalize(LocalDateTime.now()) + + if (!normalized.isPositive()) { + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) + } + } + + parsed = result + } catch (e: InvalidTimeUnitException) { + val message = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } /** diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt index 12542dc341..82396130ff 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationCoalescingConverter.kt @@ -17,6 +17,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import mu.KotlinLogging @@ -124,9 +125,6 @@ public class T4JDurationCoalescingConverter( return durations.size } - override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = - StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } - private suspend fun throwIfNecessary( e: Exception, context: CommandContext, @@ -149,6 +147,28 @@ public class T4JDurationCoalescingConverter( } else { logger.debug(e) { "Error thrown during duration parsing" } } + + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = + StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val arg = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + this.parsed = T4JDurationParser.parse(arg, context.getLocale()) + } catch (e: InvalidTimeUnitException) { + val message = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } /** diff --git a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt index b5530a2314..21b6fcc604 100644 --- a/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt +++ b/modules/time4j/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/time/time4j/T4JDurationConverter.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.parser.StringParser import com.kotlindiscord.kord.extensions.parsers.DurationParserException import com.kotlindiscord.kord.extensions.parsers.InvalidTimeUnitException import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import net.time4j.Duration @@ -60,6 +61,25 @@ public class T4JDurationConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val arg = (option as? OptionValue.StringOptionValue)?.value ?: return false + + try { + this.parsed = T4JDurationParser.parse(arg, context.getLocale()) + } catch (e: InvalidTimeUnitException) { + val message = context.translate( + "converters.duration.error.invalidUnit", + replacements = arrayOf(e.unit) + ) + if (longHelp) "\n\n" + context.translate("converters.duration.help") else "" + + throw DiscordRelayedException(message) + } catch (e: DurationParserException) { + throw DiscordRelayedException(e.error) + } + + return true + } } /** diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt index 57143b8b81..a867bbba90 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/converters/UnionConverter.kt @@ -4,6 +4,7 @@ ConverterToMulti::class, ConverterToOptional::class ) +@file:Suppress("StringLiteralDuplication") package com.kotlindiscord.kord.extensions.modules.unsafe.converters @@ -16,6 +17,7 @@ import com.kotlindiscord.kord.extensions.i18n.TranslationsProvider import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI import com.kotlindiscord.kord.extensions.parser.StringParser import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import org.koin.core.component.inject @@ -177,6 +179,107 @@ public class UnionConverter( override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + for (converter in converters) { + @Suppress("TooGenericExceptionCaught") + when (converter) { + is SingleConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result) { + converter.parseSuccess = true + this.parsed = converter.parsed + + return true + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + is DefaultingConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result) { + converter.parseSuccess = true + this.parsed = converter.parsed + + return true + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + is OptionalConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result && converter.parsed != null) { + converter.parseSuccess = true + this.parsed = converter.parsed!! + + return true + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + is MultiConverter<*> -> throw DiscordRelayedException( + context.translate( + "converters.union.error.unknownConverterType", + replacements = arrayOf(converter) + ) + ) + + is CoalescingConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result) { + converter.parseSuccess = true + this.parsed = converter.parsed + + return true + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + is DefaultingCoalescingConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result) { + converter.parseSuccess = true + this.parsed = converter.parsed + + return true + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + is OptionalCoalescingConverter<*> -> try { + val result: Boolean = converter.parseOption(context, option) + + if (result && converter.parsed != null) { + converter.parseSuccess = true + this.parsed = converter.parsed!! + + return result + } + } catch (t: Throwable) { + if (shouldThrow) throw t + } + + else -> throw DiscordRelayedException( + context.translate( + "converters.union.error.unknownConverterType", + replacements = arrayOf(converter) + ) + ) + } + } + + return false + } } /** From fbcc35ffb244e30c0550e266b57b3d1f9fe8024d Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 5 Oct 2021 18:21:37 +0100 Subject: [PATCH 121/131] [#86] Component callback registry --- .../builders/ExtensibleBotBuilder.kt | 13 +++++ .../components/ComponentWithAction.kt | 8 +++ .../buttons/EphemeralInteractionButton.kt | 12 +++++ .../buttons/PublicInteractionButton.kt | 12 +++++ .../components/callbacks/ComponentCallback.kt | 25 +++++++++ .../callbacks/ComponentCallbackRegistry.kt | 52 +++++++++++++++++++ .../components/menus/EphemeralSelectMenu.kt | 12 +++++ .../components/menus/PublicSelectMenu.kt | 12 +++++ 8 files changed, 146 insertions(+) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index d9bb9297f8..f8aaac2afa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -13,6 +13,7 @@ import com.kotlindiscord.kord.extensions.commands.application.ApplicationCommand import com.kotlindiscord.kord.extensions.commands.application.DefaultApplicationCommandRegistry import com.kotlindiscord.kord.extensions.commands.chat.ChatCommandRegistry import com.kotlindiscord.kord.extensions.components.ComponentRegistry +import com.kotlindiscord.kord.extensions.components.callbacks.ComponentCallbackRegistry import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.i18n.ResourceBundleTranslations import com.kotlindiscord.kord.extensions.i18n.SupportedLocales @@ -311,6 +312,7 @@ public open class ExtensibleBotBuilder { loadModule { single { i18nBuilder.translationsProvider } bind TranslationsProvider::class } loadModule { single { chatCommandsBuilder.registryBuilder() } bind ChatCommandRegistry::class } loadModule { single { componentsBuilder.registryBuilder() } bind ComponentRegistry::class } + loadModule { single { componentsBuilder.callbackRegistryBuilder() } bind ComponentCallbackRegistry::class } loadModule { single { @@ -402,9 +404,20 @@ public open class ExtensibleBotBuilder { /** Builder used to configure the bot's components settings. **/ @BotBuilderDSL public class ComponentsBuilder { + /** @suppress Component callback registry builder. **/ + public var callbackRegistryBuilder: () -> ComponentCallbackRegistry = ::ComponentCallbackRegistry + /** @suppress Component registry builder. **/ public var registryBuilder: () -> ComponentRegistry = ::ComponentRegistry + /** + * Register a builder (usually a constructor) returning a [ComponentCallbackRegistry] instance, which may + * be useful if you need to register a custom subclass. + */ + public fun callbackRegistry(builder: () -> ComponentCallbackRegistry) { + callbackRegistryBuilder = builder + } + /** * Register a builder (usually a constructor) returning a [ComponentRegistry] instance, which may be useful * if you need to register a custom subclass. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt index 9c52fd3291..6eb9fe943c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/ComponentWithAction.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components import com.kotlindiscord.kord.extensions.DiscordRelayedException import com.kotlindiscord.kord.extensions.checks.types.Check import com.kotlindiscord.kord.extensions.checks.types.CheckContext +import com.kotlindiscord.kord.extensions.components.callbacks.ComponentCallbackRegistry import com.kotlindiscord.kord.extensions.types.Lockable import com.kotlindiscord.kord.extensions.utils.getLocale import com.kotlindiscord.kord.extensions.utils.permissionsForMember @@ -14,6 +15,7 @@ import dev.kord.core.event.interaction.ComponentInteractionCreateEvent import kotlinx.coroutines.sync.Mutex import mu.KLogger import mu.KotlinLogging +import org.koin.core.component.inject /** * Abstract class representing a component with both an ID and executable action. @@ -29,6 +31,9 @@ public abstract class ComponentWithAction Unit + /** Use a registered callback instead of a provided [action]. Not evaluated until execution happens. **/ + public abstract fun useCallback(id: String) + /** Call this to supply a component [body], to be called when the component is interacted with. **/ public fun action(action: suspend C.() -> Unit) { body = action diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index f106f8ab11..d48e016564 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.callbacks.EphemeralButtonCallback import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task @@ -30,6 +31,17 @@ public open class EphemeralInteractionButton( initialResponseBuilder = body } + override fun useCallback(id: String) { + action { + val callback: EphemeralButtonCallback = callbackRegistry.getOfTypeOrNull(id) + ?: error("Callback \"$id\" is either missing or is the wrong type.") + + with(callback) { + invoke() + } + } + } + override fun apply(builder: ActionRowBuilder) { builder.interactionButton(style, id) { emoji = partialEmoji diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 4b481dab2c..1e4500729b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.buttons import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.callbacks.PublicButtonCallback import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task @@ -31,6 +32,17 @@ public open class PublicInteractionButton( initialResponseBuilder = body } + override fun useCallback(id: String) { + action { + val callback: PublicButtonCallback = callbackRegistry.getOfTypeOrNull(id) + ?: error("Callback \"$id\" is either missing or is the wrong type.") + + with(callback) { + invoke() + } + } + } + override fun apply(builder: ActionRowBuilder) { builder.interactionButton(style, id) { emoji = partialEmoji diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt new file mode 100644 index 0000000000..9e93f1393d --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt @@ -0,0 +1,25 @@ +package com.kotlindiscord.kord.extensions.components.callbacks + +import com.kotlindiscord.kord.extensions.components.ComponentContext +import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteractionButtonContext +import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButtonContext +import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenuContext +import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenuContext + +/** Functional interface representing a component callback. **/ +public fun interface ComponentCallback> { + /** Function used to invoke the callback. **/ + public fun ContextType.invoke() +} + +/** Component callback for an ephemeral button. **/ +public fun interface EphemeralButtonCallback : ComponentCallback + +/** Component callback for a public button. **/ +public fun interface PublicButtonCallback : ComponentCallback + +/** Component callback for an ephemeral select menu. **/ +public fun interface EphemeralMenuCallback : ComponentCallback + +/** Component callback for a public select menu. **/ +public fun interface PublicMenuCallback : ComponentCallback diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt new file mode 100644 index 0000000000..7d9943416b --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt @@ -0,0 +1,52 @@ +package com.kotlindiscord.kord.extensions.components.callbacks + +import com.kotlindiscord.kord.extensions.registry.DefaultLocalRegistryStorage +import com.kotlindiscord.kord.extensions.registry.RegistryStorage + +/** + * Very simple registry for keeping track of registered component callbacks. + * + * Intended to be extended for more advanced use-cases. + */ +public open class ComponentCallbackRegistry { + /** Component callback storage, keyed by unique ID. **/ + public open val storage: RegistryStorage> = DefaultLocalRegistryStorage() + + /** Register a public button callback to the given ID. **/ + public open suspend fun registerForPublicButton(id: String, callback: PublicButtonCallback): Unit = + storage.set(id, callback) + + /** Register an ephemeral button callback to the given ID. **/ + public open suspend fun registerForEphemeralButton(id: String, callback: EphemeralButtonCallback): Unit = + storage.set(id, callback) + + /** Register a public select menu callback to the given ID. **/ + public open suspend fun registerForPublicMenu(id: String, callback: PublicMenuCallback): Unit = + storage.set(id, callback) + + /** Register an ephemeral select menu callback to the given ID. **/ + public open suspend fun registerForEphemeralMenu(id: String, callback: EphemeralMenuCallback): Unit = + storage.set(id, callback) + + /** Get a generic component callback object for the given ID, throwing if it doesn't exist. **/ + public open suspend fun get(id: String): ComponentCallback<*> = + storage.get(id) ?: error("No callback registered for ID: $id") + + /** Get a generic component callback object for the given ID, or null if it doesn't exist. **/ + public open suspend fun getOrNull(id: String): ComponentCallback<*>? = + storage.get(id) + + /** Get a typed component callback object for the given ID, throwing if it doesn't exist or is the wrong type. **/ + public suspend inline fun > getOfType(id: String): T { + val callback = storage.get(id) ?: error("No callback registered for ID: $id") + + return callback as T + } + + /** Get a typed component callback object for the given ID, or null if it doesn't exist or is the wrong type. **/ + public suspend inline fun > getOfTypeOrNull(id: String): T? { + val callback = storage.get(id) + + return callback as? T + } +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index 501fc9ecd7..90b4ff9beb 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.components.menus import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.components.callbacks.EphemeralMenuCallback import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task @@ -23,6 +24,17 @@ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu Date: Tue, 5 Oct 2021 21:22:56 +0100 Subject: [PATCH 122/131] [#86] Callbacks now have checks and actions --- .../buttons/EphemeralInteractionButton.kt | 11 ++- .../buttons/PublicInteractionButton.kt | 11 ++- .../components/callbacks/ComponentCallback.kt | 84 +++++++++++++++++-- .../callbacks/ComponentCallbackRegistry.kt | 50 +++++++++-- .../components/menus/EphemeralSelectMenu.kt | 11 ++- .../components/menus/PublicSelectMenu.kt | 11 ++- .../kord/extensions/test/bot/Bot.kt | 2 +- .../kord/extensions/test/bot/TestExtension.kt | 3 +- 8 files changed, 152 insertions(+), 31 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index d48e016564..85f30fe633 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -36,9 +36,14 @@ public open class EphemeralInteractionButton( val callback: EphemeralButtonCallback = callbackRegistry.getOfTypeOrNull(id) ?: error("Callback \"$id\" is either missing or is the wrong type.") - with(callback) { - invoke() - } + callback.call(this) + } + + check { + val callback: EphemeralButtonCallback = callbackRegistry.getOfTypeOrNull(id) + ?: error("Callback \"$id\" is either missing or is the wrong type.") + + passed = callback.runChecks(event) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 1e4500729b..4a81f9edf4 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -37,9 +37,14 @@ public open class PublicInteractionButton( val callback: PublicButtonCallback = callbackRegistry.getOfTypeOrNull(id) ?: error("Callback \"$id\" is either missing or is the wrong type.") - with(callback) { - invoke() - } + callback.call(this) + } + + check { + val callback: PublicButtonCallback = callbackRegistry.getOfTypeOrNull(id) + ?: error("Callback \"$id\" is either missing or is the wrong type.") + + passed = callback.runChecks(event) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt index 9e93f1393d..cc561b7a56 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallback.kt @@ -1,25 +1,93 @@ package com.kotlindiscord.kord.extensions.components.callbacks +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.checks.types.Check +import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.components.ComponentContext import com.kotlindiscord.kord.extensions.components.buttons.EphemeralInteractionButtonContext import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButtonContext import com.kotlindiscord.kord.extensions.components.menus.EphemeralSelectMenuContext import com.kotlindiscord.kord.extensions.components.menus.PublicSelectMenuContext +import com.kotlindiscord.kord.extensions.utils.getLocale +import dev.kord.core.event.interaction.ButtonInteractionCreateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -/** Functional interface representing a component callback. **/ -public fun interface ComponentCallback> { - /** Function used to invoke the callback. **/ - public fun ContextType.invoke() +/** Sealed class representing a component callback. **/ +public sealed class ComponentCallback, E : InteractionCreateEvent> { + /** @suppress List of checks stored within this callback. **/ + protected open val checkList: MutableList> = mutableListOf() + + /** @suppress Action body, to be called when the component is interacted with. **/ + protected lateinit var body: suspend C.() -> Unit + + /** Call this to supply a callback [body], to be called when the component is interacted with. **/ + public fun action(action: suspend C.() -> Unit) { + body = action + } + + /** + * Define a check which must pass for the callback to be executed. + * + * A callback may have multiple checks - all checks must pass for the callback's [body] to be executed. + * Checks will be run in the order that they're defined. + * + * This function can be used DSL-style with a given body, or it can be passed one or more + * predefined functions. See the samples for more information. + * + * @param checks Checks to apply to this command. + */ + public open fun check(vararg checks: Check) { + checks.forEach { checkList.add(it) } + } + + /** + * Overloaded check function to allow for DSL syntax. + * + * @param check Check to apply to this command. + */ + public open fun check(check: Check) { + checkList.add(check) + } + + /** Runs the checks that are defined for this callback. **/ + @Throws(DiscordRelayedException::class) + public open suspend fun runChecks(event: E): Boolean { + val locale = event.getLocale() + + checkList.forEach { check -> + val context = CheckContext(event, locale) + + check(context) + + if (!context.passed) { + context.throwIfFailedWithMessage() + + return false + } + } + + return true + } + + /** Function used to run the callback's body. **/ + public open suspend fun call(context: C) { + body(context) + } } /** Component callback for an ephemeral button. **/ -public fun interface EphemeralButtonCallback : ComponentCallback +public class EphemeralButtonCallback : + ComponentCallback() /** Component callback for a public button. **/ -public fun interface PublicButtonCallback : ComponentCallback +public class PublicButtonCallback : + ComponentCallback() /** Component callback for an ephemeral select menu. **/ -public fun interface EphemeralMenuCallback : ComponentCallback +public class EphemeralMenuCallback : + ComponentCallback() /** Component callback for a public select menu. **/ -public fun interface PublicMenuCallback : ComponentCallback +public class PublicMenuCallback : + ComponentCallback() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt index 7d9943416b..de3c6b7f8c 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/callbacks/ComponentCallbackRegistry.kt @@ -10,41 +10,73 @@ import com.kotlindiscord.kord.extensions.registry.RegistryStorage */ public open class ComponentCallbackRegistry { /** Component callback storage, keyed by unique ID. **/ - public open val storage: RegistryStorage> = DefaultLocalRegistryStorage() + public open val storage: RegistryStorage> = DefaultLocalRegistryStorage() /** Register a public button callback to the given ID. **/ - public open suspend fun registerForPublicButton(id: String, callback: PublicButtonCallback): Unit = + public open suspend fun registerForPublicButton( + id: String, + builder: suspend PublicButtonCallback.() -> Unit + ) { + val callback = PublicButtonCallback() + + builder(callback) + storage.set(id, callback) + } /** Register an ephemeral button callback to the given ID. **/ - public open suspend fun registerForEphemeralButton(id: String, callback: EphemeralButtonCallback): Unit = + public open suspend fun registerForEphemeralButton( + id: String, + builder: suspend EphemeralButtonCallback.() -> Unit + ) { + val callback = EphemeralButtonCallback() + + builder(callback) + storage.set(id, callback) + } /** Register a public select menu callback to the given ID. **/ - public open suspend fun registerForPublicMenu(id: String, callback: PublicMenuCallback): Unit = + public open suspend fun registerForPublicMenu( + id: String, + builder: suspend PublicMenuCallback.() -> Unit + ) { + val callback = PublicMenuCallback() + + builder(callback) + storage.set(id, callback) + } /** Register an ephemeral select menu callback to the given ID. **/ - public open suspend fun registerForEphemeralMenu(id: String, callback: EphemeralMenuCallback): Unit = + public open suspend fun registerForEphemeralMenu( + id: String, + builder: suspend EphemeralMenuCallback.() -> Unit + ) { + val callback = EphemeralMenuCallback() + + builder(callback) + storage.set(id, callback) + } /** Get a generic component callback object for the given ID, throwing if it doesn't exist. **/ - public open suspend fun get(id: String): ComponentCallback<*> = + public open suspend fun get(id: String): ComponentCallback<*, *> = storage.get(id) ?: error("No callback registered for ID: $id") /** Get a generic component callback object for the given ID, or null if it doesn't exist. **/ - public open suspend fun getOrNull(id: String): ComponentCallback<*>? = + public open suspend fun getOrNull(id: String): ComponentCallback<*, *>? = storage.get(id) /** Get a typed component callback object for the given ID, throwing if it doesn't exist or is the wrong type. **/ - public suspend inline fun > getOfType(id: String): T { + public suspend inline fun > getOfType(id: String): T { val callback = storage.get(id) ?: error("No callback registered for ID: $id") return callback as T } /** Get a typed component callback object for the given ID, or null if it doesn't exist or is the wrong type. **/ - public suspend inline fun > getOfTypeOrNull(id: String): T? { + public suspend inline fun > getOfTypeOrNull(id: String): T? { val callback = storage.get(id) return callback as? T diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index 90b4ff9beb..054d50d670 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -29,9 +29,14 @@ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu Date: Wed, 6 Oct 2021 14:10:00 +0100 Subject: [PATCH 123/131] Phishing module --- .github/workflows/ci.yml | 2 +- extra-modules/extra-phishing/README.md | 56 ++++ extra-modules/extra-phishing/build.gradle.kts | 33 ++ .../modules/extra/phishing/DetectionAction.kt | 22 ++ .../extra/phishing/ExtPhishingBuilder.kt | 85 +++++ .../modules/extra/phishing/Functions.kt | 16 + .../modules/extra/phishing/PhishingApi.kt | 43 +++ .../extra/phishing/PhishingExtension.kt | 300 ++++++++++++++++++ .../kord/extensions/test/bot/Bot.kt | 31 ++ .../test/resources/junit-platform.properties | 1 + .../src/test/resources/logback.groovy | 30 ++ .../application/ApplicationCommand.kt | 2 +- libs.versions.toml | 2 + settings.gradle.kts | 1 + 14 files changed, 622 insertions(+), 2 deletions(-) create mode 100644 extra-modules/extra-phishing/README.md create mode 100644 extra-modules/extra-phishing/build.gradle.kts create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DetectionAction.kt create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/ExtPhishingBuilder.kt create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/Functions.kt create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt create mode 100644 extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt create mode 100644 extra-modules/extra-phishing/src/test/resources/junit-platform.properties create mode 100644 extra-modules/extra-phishing/src/test/resources/logback.groovy diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 556e83db4e..ce319e3adc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: with: name: JARs (Extra Modules) - path: extra/*/build/libs/*.jar + path: extra-modules/*/build/libs/*.jar - name: Upload artifact (Main JARs) uses: actions/upload-artifact@v2 diff --git a/extra-modules/extra-phishing/README.md b/extra-modules/extra-phishing/README.md new file mode 100644 index 0000000000..0a3a2e0c70 --- /dev/null +++ b/extra-modules/extra-phishing/README.md @@ -0,0 +1,56 @@ +# Phishing Extension + +[![Discord: Click here](https://img.shields.io/static/v1?label=Discord&message=Click%20here&color=7289DA&style=for-the-badge&logo=discord)](https://discord.gg/gjXqqCS) [![Release](https://img.shields.io/nexus/r/com.kotlindiscord.kord.extensions/extra-phishing?nexusVersion=3&logo=gradle&color=blue&label=Release&server=https%3A%2F%2Fmaven.kotlindiscord.com&style=for-the-badge)](https://maven.kotlindiscord.com/#browse/browse:maven-releases:com%2Fkotlindiscord%2Fkord%2Fextensions%2Fextra-phishing) [![Snapshot](https://img.shields.io/nexus/s/com.kotlindiscord.kord.extensions/extra-phishing?logo=gradle&color=orange&label=Snapshot&server=https%3A%2F%2Fmaven.kotlindiscord.com&style=for-the-badge)](https://maven.kotlindiscord.com/#browse/browse:maven-snapshots:com%2Fkotlindiscord%2Fkord%2Fextensions%2Fextra-phishing) + +This module contains an extension written to provide some anti-phishing protection, based on the crowdsourced [Sinking Yachts API](https://phish.sinking.yachts/docs). + +# Getting Started + +* **Maven repo:** `https://maven.kotlindiscord.com/repository/maven-public/` +* **Maven coordinates:** `com.kotlindiscord.kord.extensions:extra-phishing:VERSION` + +At its simplest, you can add this extension directly to your bot with a minimum configuration. For example: + +```kotlin +suspend fun main() { + val bot = ExtensibleBot(System.getenv("TOKEN")) { + + extensions { + extPhishing { + appName = "My Bot" + } + } + } + + bot.start() +} +``` + +This will install the extension using its default configuration. However, the extension may be configured in several ways - as is detailed below. + +# Commands + +This extension provides a number of commands for use on Discord. + +* Slash command: `/phishing-check`, for checking whether the given argument is a phishing domain +* Message command: `Phishing Check`, for manually checking a specific message via the right-click menu + +Access to both commands can be limited to a specific Discord permission. This can be configured below, but defaults to "Manage Messages". + +# Configuration + +To configure this module, values can be provided within the `extPhishing` builder. + +* **Required:** `appName` - Give your application a name so that Sinking Yachts can identify it for statistics purposes + +* `detectionAction` (default: Delete) - What to do when a message containing a phishing domain is detected +* `logChannelName` (default: "logs") - The name of the channel to use for logging; the extension will search the channels present on the current server and use the last one with an exactly-matching name +* `notifyUser` (default: True) - Whether to DM the user, letting them know they posted a phishing domain and what action was taken +* `requiredCommandPermission` (default: Manage Server) - The permission a user must have in order to run the bundled message and slash commands +* `updateDelay` (default: 15 minutes) - How often to check for new phishing domains, five minutes at minimum +* `urlRegex` (default: [The Perfect URL Regex](https://urlregex.com/)) - A regular expression used to extract domains from messages, with **exactly one capturing group containing the entire domain** (and optionally the URL path) + +Additionally, the following configuration functions are available: + +* `check` - Used to define checks that must pass for event handlers to be run, and thus for messages to be checked (you could use this to exempt your staff, for example) +* `regex` - A convenience function for registering a `String` as URL regex, with the case-insensitive flag diff --git a/extra-modules/extra-phishing/build.gradle.kts b/extra-modules/extra-phishing/build.gradle.kts new file mode 100644 index 0000000000..1ea8eedb60 --- /dev/null +++ b/extra-modules/extra-phishing/build.gradle.kts @@ -0,0 +1,33 @@ +plugins { + `kordex-module` + `published-module` + `dokka-module` + `disable-explicit-api-mode` +} + +repositories { + maven { + name = "KotDis" + url = uri("https://maven.kotlindiscord.com/repository/maven-public/") + } +} + +dependencies { + detektPlugins(libs.detekt) + + implementation(libs.logging) + implementation(libs.kotlin.stdlib) + implementation(libs.ktor.logging) + + testImplementation(libs.groovy) // For logback config + testImplementation(libs.logback) + + implementation(project(":kord-extensions")) +} + +group = "com.kotlindiscord.kord.extensions" + +kordex { + jvmTarget.set("9") + javaVersion.set(JavaVersion.VERSION_1_9) +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DetectionAction.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DetectionAction.kt new file mode 100644 index 0000000000..2122d61075 --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DetectionAction.kt @@ -0,0 +1,22 @@ +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +/** + * Sealed class representing what should happen when a phishing link is detected. + * + * The extension will always try to log the message, but you can specify [LogOnly] if that's _all_ you want. + * + * @property message Message to return to the user. + */ +sealed class DetectionAction(val message: String) { + /** Ban 'em and delete the message. **/ + object Ban : DetectionAction("you have been banned from the server") + + /** Delete the message. **/ + object Delete : DetectionAction("it has been deleted") + + /** Kick 'em and delete the message. **/ + object Kick : DetectionAction("you have been kicked from the server") + + /** Don't do anything, just log it in the logs channel. **/ + object LogOnly : DetectionAction("it has been logged for the server staff to review") +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/ExtPhishingBuilder.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/ExtPhishingBuilder.kt new file mode 100644 index 0000000000..2ff7b4e4cd --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/ExtPhishingBuilder.kt @@ -0,0 +1,85 @@ +@file:OptIn(ExperimentalTime::class) +@file:Suppress("MagicNumber") + +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +import com.kotlindiscord.kord.extensions.checks.types.Check +import dev.kord.common.entity.Permission +import dev.kord.core.event.Event +import kotlin.time.Duration +import kotlin.time.ExperimentalTime + +/** Builder used to configure the phishing extension. **/ +class ExtPhishingBuilder { + /** The name of your application, which allows the Sinking Yachts maintainers to identify what it is. **/ + lateinit var appName: String + + /** Delay between domain update checks, 5 minutes at minimum. **/ + var updateDelay = Duration.minutes(15) + + /** + * Regular expression used to extract domains from messages. + * + * If you customize this, it must contain exactly one capturing group, containing the full domain name, and + * optionally the path, if this is an actual URL. You can mark a group as non-capturing by prefixing it with + * `?:`. + * + * The provided regex comes from https://urlregex.com/ - but you can provide a different regex if you need + * detection to be more sensitive than just clickable links. + */ + var urlRegex = "(?:https?|ftp|file|discord)://([-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])" + .toRegex(RegexOption.IGNORE_CASE) + + /** @suppress List of checks to apply to event handlers. **/ + val checks: MutableList> = mutableListOf() + + /** + * If you want to require a permission for the phishing check commands, supply it here. Alternatively, supply + * `null` and everyone will be given access to them. + */ + var requiredCommandPermission: Permission? = Permission.ManageMessages + + /** + * What to do when a message creation/edit contains a phishing domain. + * + * @see DetectionAction + */ + var detectionAction: DetectionAction = DetectionAction.Delete + + /** Whether to DM users when their messages contain phishing domains, with the action taken. **/ + var notifyUser = true + + /** + * The name of the logs channel to use for detection messages, if not "logs". + * + * The extension will try to find the last channel in the channel list with a name exactly matching the + * given name here, "logs" by default. + */ + var logChannelName = "logs" + + /** Register a check that must pass in order for an event handler to run, and for messages to be processed. **/ + fun check(check: Check) { + checks.add(check) + } + + /** Register checks that must pass in order for an event handler to run, and for messages to be processed. **/ + fun check(vararg checkList: Check) { + checks.addAll(checkList) + } + + /** Convenience function for supplying a case-insensitive [urlRegex]. **/ + fun regex(pattern: String) { + urlRegex = pattern.toRegex(RegexOption.IGNORE_CASE) + } + + /** @suppress **/ + fun validate() { + if (!this::appName.isInitialized) { + error("Application name must be provided") + } + + if (updateDelay < Duration.minutes(5)) { + error("The update delay must be at least five minutes - don't spam the API!") + } + } +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/Functions.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/Functions.kt new file mode 100644 index 0000000000..deb9008f49 --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/Functions.kt @@ -0,0 +1,16 @@ +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +import com.kotlindiscord.kord.extensions.builders.ExtensibleBotBuilder + +/** + * Configure the phishing extension and add it to the bot. + */ +inline fun ExtensibleBotBuilder.ExtensionsBuilder.extPhishing(builder: ExtPhishingBuilder.() -> Unit) { + val settings = ExtPhishingBuilder() + + builder(settings) + + settings.validate() + + add { PhishingExtension(settings) } +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt new file mode 100644 index 0000000000..d2aa276d68 --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt @@ -0,0 +1,43 @@ +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +import io.ktor.client.HttpClient +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.features.logging.LogLevel +import io.ktor.client.features.logging.Logging +import io.ktor.client.request.get +import io.ktor.client.request.header + +internal const val ALL_PATH = "https://phish.sinking.yachts/all" +internal const val CHECK_PATH = "https://phish.sinking.yachts/check/%" +internal const val RECENT_PATH = "https://phish.sinking.yachts/recent/%" +internal const val SIZE_PATH = "https://phish.sinking.yachts/dbsize" + +/** Implementation of the Sinking Yachts phishing domain API. **/ +class PhishingApi(internal val appName: String) { + internal val client = HttpClient { + install(JsonFeature) + install(Logging) { + level = LogLevel.INFO + } + } + + internal suspend inline fun get(url: String): T = client.get(url) { + header("X-Identity", "$appName (via Kord Extensions)") + } + + /** Get all known phishing domains from the API. **/ + suspend fun getAllDomains(): Set = + get(ALL_PATH) + + /** Query the API directly to check a specific domain. **/ + suspend fun checkDomain(domain: String): Boolean = + get(CHECK_PATH.replace("%", domain)) + + /** Get all new phishing domains added in the previous [seconds] seconds. **/ + suspend fun getRecentDomains(seconds: Long): Set = + get(RECENT_PATH.replace("%", seconds.toString())) + + /** Get the total number of phishing domains that the API knows about. **/ + suspend fun getTotalDomains(): Long = + get(SIZE_PATH) +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt new file mode 100644 index 0000000000..9469164048 --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt @@ -0,0 +1,300 @@ +@file:OptIn(ExperimentalTime::class) +@file:Suppress("StringLiteralDuplication") + +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +import com.kotlindiscord.kord.extensions.DISCORD_RED +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.checks.hasPermission +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.converters.impl.string +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand +import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand +import com.kotlindiscord.kord.extensions.extensions.event +import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.dm +import com.kotlindiscord.kord.extensions.utils.getJumpUrl +import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler +import com.kotlindiscord.kord.extensions.utils.scheduling.Task +import dev.kord.core.behavior.ban +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Message +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.event.message.MessageUpdateEvent +import dev.kord.rest.builder.message.create.embed +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.lastOrNull +import kotlinx.coroutines.launch +import mu.KotlinLogging +import kotlin.time.ExperimentalTime + +/** Phishing extension, responsible for checking for phishing domains in messages. **/ +class PhishingExtension(private val settings: ExtPhishingBuilder) : Extension() { + override val name = "phishing" + + private val api = PhishingApi(settings.appName) + private val domainCache: MutableSet = mutableSetOf() + private val logger = KotlinLogging.logger { } + + private val scheduler = Scheduler() + private var checkTask: Task? = null + + override suspend fun setup() { + domainCache.addAll(api.getAllDomains()) + + checkTask = scheduler.schedule(settings.updateDelay, pollingSeconds = 30, callback = ::updateDomains) + + event { + check { isNotBot() } + check { event.message.author != null } + check { event.guildId != null } + + check { + settings.checks.forEach { + if (passed) it() + } + } + + action { + handleMessage(event.message) + } + } + + event { + check { isNotBot() } + check { event.new.author.value != null } + check { event.new.guildId.value != null } + + check { + settings.checks.forEach { + if (passed) it() + } + } + + action { + handleMessage(event.message.asMessage()) + } + } + + ephemeralMessageCommand { + name = "Phishing Check" + + if (this@PhishingExtension.settings.requiredCommandPermission != null) { + check { hasPermission(this@PhishingExtension.settings.requiredCommandPermission!!) } + } + + action { + for (message in targetMessages) { + val domains = parseDomains(message.content.lowercase()) + val matches = domains intersect domainCache + + respond { + content = if (matches.isNotEmpty()) { + "⚠️ [Message ${message.id.value}](${message.getJumpUrl()}) " + + "**contains ${matches.size} phishing link/s**." + } else { + "✅ [Message ${message.id.value}](${message.getJumpUrl()}) " + + "**does not contain any phishing links**." + } + } + } + } + } + + ephemeralSlashCommand(::DomainArgs) { + name = "phishing-check" + description = "Check whether a given domain is a known phishing domain." + + if (this@PhishingExtension.settings.requiredCommandPermission != null) { + check { hasPermission(this@PhishingExtension.settings.requiredCommandPermission!!) } + } + + action { + respond { + content = if (domainCache.contains(arguments.domain.lowercase())) { + "⚠️ `${arguments.domain}` is a known phishing domain." + } else { + "✅ `${arguments.domain}` is not a known phishing domain." + } + } + } + } + } + + internal suspend fun handleMessage(message: Message) { + val domains = parseDomains(message.content.lowercase()) + val matches = domains intersect domainCache + + if (matches.isNotEmpty()) { + logger.debug { "Found a message with ${matches.size} phishing domains." } + + if (settings.notifyUser) { + message.kord.launch { + message.author!!.dm { + content = "We've detected that the following message contains a phishing domain. For this " + + "reason, **${settings.detectionAction.message}**." + + embed { + title = "Phishing domain detected" + description = message.content + color = DISCORD_RED + + field { + inline = true + + name = "Channel" + value = message.channel.mention + } + + field { + inline = true + + name = "Message ID" + value = "`${message.id.value}`" + } + + field { + inline = true + + name = "Server" + value = message.getGuild().name + } + } + } + } + } + + when (settings.detectionAction) { + DetectionAction.Ban -> { + message.getAuthorAsMember()!!.ban { + reason = "Message contained a phishing domain" + } + + message.delete("Message contained a phishing domain") + } + + DetectionAction.Delete -> message.delete("Message contained a phishing domain") + + DetectionAction.Kick -> { + message.getAuthorAsMember()!!.kick("Message contained a phishing domain") + message.delete("Message contained a phishing domain") + } + + DetectionAction.LogOnly -> { + // Do nothing, we always log + } + } + + logDeletion(message, matches) + } + } + + internal suspend fun logDeletion(message: Message, matches: Set) { + val guild = message.getGuild() + + val channel = message + .getGuild() + .channels + .filter { it.name == settings.logChannelName } + .lastOrNull() + ?.asChannelOrNull() as? GuildMessageChannel + + if (channel == null) { + logger.warn { + "Unable to find a channel named ${settings.logChannelName} on ${guild.name} (${guild.id.value})" + } + + return + } + + val matchList = "# Phishing Domain Matches\n\n" + + "**Total:** ${matches.size}\n\n" + + matches.joinToString("\n") { "* `$it`" } + + channel.createMessage { + addFile("matches.md", matchList.byteInputStream()) + + embed { + title = "Phishing domain detected" + description = message.content + color = DISCORD_RED + + field { + inline = true + + name = "Author" + value = "${message.author!!.mention} (" + + "`${message.author!!.tag}` / " + + "`${message.author!!.id.value}`" + + ")" + } + + field { + inline = true + + name = "Channel" + value = "${message.channel.mention} (`${message.channelId.value}`)" + } + + field { + inline = true + + name = "Message" + value = "[`${message.id.value}`](${message.getJumpUrl()})" + } + + field { + inline = true + + name = "Total Matches" + value = matches.size.toString() + } + } + } + } + + internal fun parseDomains(content: String): MutableSet { + val domains: MutableSet = mutableSetOf() + + for (match in settings.urlRegex.findAll(content)) { + var found = match.groups[1]!!.value.trim('/') + + if ("/" in found) { + found = found.split("/", limit = 2).first() + } + + domains.add(found) + } + + logger.debug { "Found ${domains.size} domains: ${domains.joinToString()}" } + + return domains + } + + override suspend fun unload() { + checkTask?.cancel() + checkTask = null + } + + @Suppress("MagicNumber") + internal suspend fun updateDomains() { + logger.trace { "Updating domains..." } + + // An extra 30 seconds for safety + domainCache.addAll(api.getRecentDomains(settings.updateDelay.inWholeSeconds + 30)) + checkTask?.restart() // Off we go again + } + + /** Arguments class for domain-relevant commands. **/ + inner class DomainArgs : Arguments() { + /** Targeted domain string. **/ + val domain by string("domain", "Domain to check") { _, value -> + if ("/" in value) { + throw DiscordRelayedException("Please provide the domain name only, without the protocol or a path.") + } + } + } +} diff --git a/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt new file mode 100644 index 0000000000..9912b7efb5 --- /dev/null +++ b/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -0,0 +1,31 @@ +package com.kotlindiscord.kord.extensions.test.bot + +import com.kotlindiscord.kord.extensions.ExtensibleBot +import com.kotlindiscord.kord.extensions.checks.isNotBot +import com.kotlindiscord.kord.extensions.modules.extra.phishing.extPhishing +import com.kotlindiscord.kord.extensions.utils.env +import org.koin.core.logger.Level + +suspend fun main() { + val bot = ExtensibleBot(env("TOKEN")!!) { + koinLogLevel = Level.DEBUG + + chatCommands { + check { isNotBot() } + enabled = true + } + + applicationCommands { + enabled = true + } + + extensions { + extPhishing { + appName = "Integration test bot" + logChannelName = "alerts" + } + } + } + + bot.start() +} diff --git a/extra-modules/extra-phishing/src/test/resources/junit-platform.properties b/extra-modules/extra-phishing/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..1d27b78fbb --- /dev/null +++ b/extra-modules/extra-phishing/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.execution.parallel.enabled=true diff --git a/extra-modules/extra-phishing/src/test/resources/logback.groovy b/extra-modules/extra-phishing/src/test/resources/logback.groovy new file mode 100644 index 0000000000..1bab8d7d2d --- /dev/null +++ b/extra-modules/extra-phishing/src/test/resources/logback.groovy @@ -0,0 +1,30 @@ +import ch.qos.logback.core.joran.spi.ConsoleTarget + +def environment = System.getenv().getOrDefault("ENVIRONMENT", "production") + +def defaultLevel = DEBUG + +if (environment == "spam") { + logger("dev.kord.rest.DefaultGateway", TRACE) +} else { + // Silence warning about missing native PRNG + logger("io.ktor.util.random", ERROR) +} + +appender("CONSOLE", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "%d{yyyy-MM-dd HH:mm:ss:SSS Z} | %5level | %40.40logger{40} | %msg%n" + } + + target = ConsoleTarget.SystemErr +} + +appender("FILE", FileAppender) { + file = "output.log" + + encoder(PatternLayoutEncoder) { + pattern = "%d{yyyy-MM-dd HH:mm:ss:SSS Z} | %5level | %40.40logger{40} | %msg%n" + } +} + +root(defaultLevel, ["CONSOLE", "FILE"]) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt index b89aaaa6d5..1e870fb6e8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/ApplicationCommand.kt @@ -176,7 +176,7 @@ public abstract class ApplicationCommand( * @param checks Checks to apply to this command. */ public open fun check(vararg checks: Check) { - checks.forEach { checkList.add(it) } + checkList.addAll(checks) } /** diff --git a/libs.versions.toml b/libs.versions.toml index 7d91766de2..dc2c43bd2c 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -14,6 +14,7 @@ konf = "0.23.0" kord = "0.8.0-M5" kotlinpoet = "1.8.0" ksp = "1.5.30-1.0.0-beta08" +ktor = "1.6.0" kx-ser = "1.2.2" linkie = "1.0.88" logback = "1.2.3" @@ -38,6 +39,7 @@ kord = { module = "dev.kord:kord-core", version.ref = "kord" } kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } +ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } kx-ser = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kx-ser" } linkie = { module = "me.shedaniel:linkie-core", version.ref = "linkie" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } diff --git a/settings.gradle.kts b/settings.gradle.kts index b736e0b20d..0e793fb0e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ include("kord-extensions") include("extra-modules:extra-common") include("extra-modules:extra-mappings") +include("extra-modules:extra-phishing") include("modules:java-time") include("modules:time4j") From e93e957fa0f5fdd38cbd4bddcc7ff47e62c738ac Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 7 Oct 2021 22:20:07 +0100 Subject: [PATCH 124/131] 1.5.1-SNAPSHOT: Kord M6, with a silly hack. Also, #101. --- .../kord/extensions/test/bot/Bot.kt | 2 +- .../kord/extensions/test/bot/Bot.kt | 2 +- gradle.properties | 2 +- .../kord/extensions/ExtensibleBot.kt | 14 ++-- .../builders/ExtensibleBotBuilder.kt | 3 +- .../kord/extensions/checks/CheckUtils.kt | 2 +- .../application/ApplicationCommandRegistry.kt | 2 +- .../DefaultApplicationCommandRegistry.kt | 2 +- .../message/EphemeralMessageCommand.kt | 4 +- .../message/PublicMessageCommand.kt | 4 +- .../slash/EphemeralSlashCommand.kt | 4 +- .../application/slash/PublicSlashCommand.kt | 4 +- .../converters/impl/NumberChoiceConverter.kt | 10 +-- .../application/user/EphemeralUserCommand.kt | 4 +- .../application/user/PublicUserCommand.kt | 4 +- .../commands/converters/impl/IntConverter.kt | 2 +- .../buttons/EphemeralInteractionButton.kt | 8 +- .../buttons/PublicInteractionButton.kt | 4 +- .../components/menus/EphemeralSelectMenu.kt | 8 +- .../components/menus/PublicSelectMenu.kt | 4 +- .../kord/extensions/events/KordExEvent.kt | 2 + .../pagination/EphemeralResponsePaginator.kt | 82 ------------------- .../pagination/PublicFollowUpPaginator.kt | 6 +- ...ponsePaginator.kt => ResponsePaginator.kt} | 14 ++-- .../kord/extensions/pagination/_Functions.kt | 8 +- .../types/EphemeralInteractionContext.kt | 41 ++++++++-- .../types/PublicInteractionContext.kt | 26 +++--- .../kord/extensions/utils/_Kord.kt | 24 ++++++ .../kord/extensions/utils/_Message.kt | 25 ------ .../kord/extensions/utils/_Users.kt | 2 +- .../extensions/utils/deltas/MemberDelta.kt | 4 +- .../kord/extensions/utils/deltas/UserDelta.kt | 5 +- libs.versions.toml | 17 ++-- .../kord/extensions/test/bot/Bot.kt | 14 ++-- .../kord/extensions/test/bot/Bot.kt | 2 +- .../unsafe/types/UnsafeInteractionContext.kt | 72 ++++++---------- 36 files changed, 179 insertions(+), 254 deletions(-) delete mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/{PublicResponsePaginator.kt => ResponsePaginator.kt} (85%) diff --git a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index eaf24c33fe..1ed5021c91 100644 --- a/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-mappings/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -7,7 +7,7 @@ import com.kotlindiscord.kord.extensions.utils.env import org.koin.core.logger.Level suspend fun main() { - val bot = ExtensibleBot(env("TOKEN")!!) { + val bot = ExtensibleBot(env("TOKEN")) { koinLogLevel = Level.DEBUG chatCommands { diff --git a/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 9912b7efb5..417f949f93 100644 --- a/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/extra-modules/extra-phishing/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -7,7 +7,7 @@ import com.kotlindiscord.kord.extensions.utils.env import org.koin.core.logger.Level suspend fun main() { - val bot = ExtensibleBot(env("TOKEN")!!) { + val bot = ExtensibleBot(env("TOKEN")) { koinLogLevel = Level.DEBUG chatCommands { diff --git a/gradle.properties b/gradle.properties index 8bb6c53762..900a4cc2f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.incremental = true ksp.incremental = false -projectVersion = 1.5.0-SNAPSHOT +projectVersion = 1.5.1-SNAPSHOT #dokka will run out of memory with the default meta space org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index e0c06c85a9..82e6f603a1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -78,10 +78,6 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva defaultStrategy = settings.cacheBuilder.defaultStrategy - if (settings.intentsBuilder != null) { - this.intents = Intents(settings.intentsBuilder!!) - } - if (settings.shardingBuilder != null) { sharding(settings.shardingBuilder!!) } @@ -100,15 +96,19 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva send(this@on) } } + + registerListeners() + addDefaultExtensions() } /** Start up the bot and log into Discord. **/ public open suspend fun start() { settings.hooksBuilder.runBeforeStart(this) - registerListeners() - - getKoin().get().login(settings.presenceBuilder) + getKoin().get().login { + this.presence(settings.presenceBuilder) + this.intents = Intents(settings.intentsBuilder!!) + } } /** This function sets up all of the bot's default event listeners. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index f8aaac2afa..bf9b06c86f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -33,13 +33,13 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.builder.kord.KordBuilder -import dev.kord.core.builder.kord.Shards import dev.kord.core.cache.KordCacheBuilder import dev.kord.core.event.message.MessageCreateEvent import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.Intents import dev.kord.gateway.builder.PresenceBuilder +import dev.kord.gateway.builder.Shards import dev.kord.rest.builder.message.create.MessageCreateBuilder import mu.KLogger import mu.KotlinLogging @@ -346,7 +346,6 @@ public open class ExtensibleBotBuilder { hooksBuilder.runCreated(bot) hooksBuilder.runBeforeExtensionsAdded(bot) - bot.addDefaultExtensions() extensionsBuilder.extensions.forEach { bot.addExtension(it) } hooksBuilder.runAfterExtensionsAdded(bot) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt index 4be0a30881..ff028fccd1 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/checks/CheckUtils.kt @@ -102,7 +102,7 @@ public suspend fun topChannelFor(event: Event): ChannelBehavior? { * @param event The event concerning to the channel to retrieve. * @return A [Long] representing the channel ID, or null if there isn't one. */ -public suspend fun channelIdFor(event: Event): Long? { +public suspend fun channelIdFor(event: Event): ULong? { return when (event) { is ChannelCreateEvent -> event.channel.id.value is ChannelDeleteEvent -> event.channel.id.value 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 ae38bfc5ef..e937d8064e 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 @@ -344,7 +344,7 @@ public abstract class ApplicationCommandRegistry : KoinComponent { ): T? { try { if (guild != null) { - kord.editApplicationCommandPermissions(kord.resources.applicationId, guild.id, commandId) { + kord.editApplicationCommandPermissions(guild.id, commandId) { injectRawPermissions(this, command) } 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 4734975d1d..49dee9d56e 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 @@ -102,7 +102,7 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry try { commandsWithPerms.forEach { (guildId, commands) -> if (guildId != null) { - kord.bulkEditApplicationCommandPermissions(kord.resources.applicationId, guildId) { + kord.bulkEditApplicationCommandPermissions(guildId) { commands.forEach { (id, commandObj) -> command(id) { injectRawPermissions(this, commandObj) } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt index b8c703bbee..5221baf95f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/EphemeralMessageCommand.kt @@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralMessageResponseBuilder = - (suspend EphemeralInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? /** Ephemeral message command. **/ public class EphemeralMessageCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt index bd1180ac7d..67be2de1f2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/message/PublicMessageCommand.kt @@ -13,10 +13,10 @@ import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.MessageCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicMessageResponseBuilder = - (suspend PublicInteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(MessageCommandInteractionCreateEvent) -> Unit)? /** Public message command. **/ public class PublicMessageCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt index 912ce7f245..5b806350c5 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/EphemeralSlashCommand.kt @@ -13,10 +13,10 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSlashResponseBuilder = - (suspend EphemeralInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? /** Ephemeral slash command. **/ public class EphemeralSlashCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt index 41cca70be2..74f08fef87 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/PublicSlashCommand.kt @@ -14,10 +14,10 @@ import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.entity.interaction.GroupCommand import dev.kord.core.entity.interaction.SubCommand import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicSlashResponseBehavior = - (suspend PublicInteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(ChatInputCommandInteractionCreateEvent) -> Unit)? /** Public slash command. **/ public class PublicSlashCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt index 62d168d3c9..ea5eb9d2cd 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt @@ -40,16 +40,16 @@ private const val DEFAULT_RADIX = 10 public class NumberChoiceConverter( private val radix: Int = DEFAULT_RADIX, - choices: Map, - override var validator: Validator = null -) : ChoiceConverter(choices) { + choices: Map, + override var validator: Validator = null +) : ChoiceConverter(choices) { override val signatureTypeString: String = "converters.number.signatureType" override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { val arg: String = named ?: parser?.parseNext()?.data ?: return false try { - this.parsed = arg.toInt(radix) + this.parsed = arg.toLong(radix) } catch (e: NumberFormatException) { val errorString = if (radix == DEFAULT_RADIX) { context.translate("converters.number.error.invalid.defaultBase", replacements = arrayOf(arg)) @@ -67,7 +67,7 @@ class NumberChoiceConverter( IntChoiceBuilder(arg.displayName, arg.description).apply { required = true - this@NumberChoiceConverter.choices.forEach { choice(it.key, it.value) } + this@NumberChoiceConverter.choices.forEach { choice(it.key, it.value.toInt()) } } override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt index b10dc73ad8..923c6030d6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/EphemeralUserCommand.kt @@ -12,10 +12,10 @@ import com.kotlindiscord.kord.extensions.types.FailureReason import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralUserResponseBuilder = - (suspend EphemeralInteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? /** Ephemeral user command. **/ public class EphemeralUserCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt index a29f03de4a..e0b862c903 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/user/PublicUserCommand.kt @@ -13,10 +13,10 @@ import com.kotlindiscord.kord.extensions.types.respond import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.UserCommandInteractionCreateEvent -import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicUserResponseBuilder = - (suspend PublicInteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(UserCommandInteractionCreateEvent) -> Unit)? /** Public user command. **/ public class PublicUserCommand( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt index 84bd340fd7..cae32c27a6 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/IntConverter.kt @@ -60,7 +60,7 @@ public class IntConverter( override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { val optionValue = (option as? OptionValue.IntOptionValue)?.value ?: return false - this.parsed = optionValue + this.parsed = optionValue.toInt() return true } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt index 85f30fe633..9a323d583f 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/EphemeralInteractionButton.kt @@ -11,10 +11,10 @@ import dev.kord.common.entity.ButtonStyle import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralButtonResponseBuilder = - (suspend EphemeralInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? /** Class representing an ephemeral-only interaction button. **/ public open class EphemeralInteractionButton( @@ -73,9 +73,9 @@ public open class EphemeralInteractionButton( event.interaction.respondEphemeral { initialResponseBuilder!!(event) } } else { if (!deferredAck) { - event.interaction.acknowledgeEphemeralDeferredMessageUpdate() - } else { event.interaction.acknowledgeEphemeral() + } else { + event.interaction.acknowledgeEphemeralDeferredMessageUpdate() } } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt index 4a81f9edf4..24f884bb63 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/buttons/PublicInteractionButton.kt @@ -12,10 +12,10 @@ import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.respondPublic import dev.kord.core.event.interaction.ButtonInteractionCreateEvent import dev.kord.rest.builder.component.ActionRowBuilder -import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialPublicButtonResponseBuilder = - (suspend PublicInteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(ButtonInteractionCreateEvent) -> Unit)? /** Class representing a public-only button component. **/ public open class PublicInteractionButton( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt index 054d50d670..5d73017741 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/components/menus/EphemeralSelectMenu.kt @@ -9,10 +9,10 @@ import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.event.interaction.SelectMenuInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder public typealias InitialEphemeralSelectMenuResponseBuilder = - (suspend EphemeralInteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? /** Class representing an ephemeral-only select (dropdown) menu. **/ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu(timeoutTask) { @@ -59,9 +59,9 @@ public open class EphemeralSelectMenu(timeoutTask: Task?) : SelectMenu Unit)? + (suspend InteractionResponseCreateBuilder.(SelectMenuInteractionCreateEvent) -> Unit)? /** Class representing a public-only select (dropdown) menu. **/ public open class PublicSelectMenu(timeoutTask: Task?) : SelectMenu(timeoutTask) { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt index a224944b5d..eb558e7e6a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/events/KordExEvent.kt @@ -3,6 +3,7 @@ package com.kotlindiscord.kord.extensions.events import dev.kord.core.Kord import dev.kord.core.event.Event import org.koin.core.component.KoinComponent +import kotlin.coroutines.CoroutineContext /** * Base interface for events fired by Kord Extensions. @@ -10,4 +11,5 @@ import org.koin.core.component.KoinComponent public interface KordExEvent : Event, KoinComponent { override val kord: Kord get() = getKoin().get() override val shard: Int get() = -1 + override val coroutineContext: CoroutineContext get() = kord.coroutineContext } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt deleted file mode 100644 index b1832f3234..0000000000 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt +++ /dev/null @@ -1,82 +0,0 @@ -@file:OptIn(KordPreview::class) - -package com.kotlindiscord.kord.extensions.pagination - -import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder -import com.kotlindiscord.kord.extensions.pagination.pages.Pages -import dev.kord.common.annotation.KordPreview -import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior -import dev.kord.core.behavior.interaction.edit -import dev.kord.core.entity.ReactionEmoji -import dev.kord.rest.builder.message.modify.embed -import java.util.* - -/** - * Class representing a button-based paginator that operates by editing the given ephemeral interaction response. - * - * @param interaction Interaction response behaviour to work with. - */ -public class EphemeralResponsePaginator( - pages: Pages, - owner: UserBehavior? = null, - timeoutSeconds: Long? = null, - switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, - bundle: String? = null, - locale: Locale? = null, - - public val interaction: EphemeralInteractionResponseBehavior, -) : BaseButtonPaginator(pages, owner, timeoutSeconds, true, switchEmoji, bundle, locale) { - /** Whether this paginator has been set up for the first time. **/ - public var isSetup: Boolean = false - - override suspend fun send() { - if (!isSetup) { - isSetup = true - - setup() - } else { - updateButtons() - } - - interaction.edit { - embed { applyPage() } - - with(this@EphemeralResponsePaginator.components) { - this@edit.applyToMessage() - } - } - } - - override suspend fun destroy() { - if (!active) { - return - } - - active = false - - interaction.edit { - embed { applyPage() } - - this.components = mutableListOf() - } - - super.destroy() - } -} - -/** Convenience function for creating an interaction button paginator from a paginator builder. **/ -@Suppress("FunctionNaming") // Factory function -public fun EphemeralResponsePaginator( - builder: PaginatorBuilder, - interaction: EphemeralInteractionResponseBehavior -): EphemeralResponsePaginator = EphemeralResponsePaginator( - pages = builder.pages, - owner = builder.owner, - timeoutSeconds = builder.timeoutSeconds, - bundle = builder.bundle, - locale = builder.locale, - interaction = interaction, - - switchEmoji = builder.switchEmoji ?: if (builder.pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, -) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt index f2d9894a3c..1097f92f2b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicFollowUpPaginator.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.ReactionEmoji @@ -30,7 +30,7 @@ public class PublicFollowUpPaginator( bundle: String? = null, locale: Locale? = null, - public val interaction: PublicInteractionResponseBehavior, + public val interaction: InteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { /** Follow-up interaction to use for this paginator's embeds. Will be created by [send]. **/ public var embedInteraction: PublicFollowupMessage? = null @@ -84,7 +84,7 @@ public class PublicFollowUpPaginator( @Suppress("FunctionNaming") // Factory function public fun PublicFollowUpPaginator( builder: PaginatorBuilder, - interaction: PublicInteractionResponseBehavior + interaction: InteractionResponseBehavior ): PublicFollowUpPaginator = PublicFollowUpPaginator( pages = builder.pages, owner = builder.owner, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt similarity index 85% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt index c0255b927d..ebe48816f8 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior +import dev.kord.core.behavior.interaction.InteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.entity.ReactionEmoji import dev.kord.rest.builder.message.modify.embed @@ -17,7 +17,7 @@ import java.util.* * * @param interaction Interaction response behaviour to work with. */ -public class PublicResponsePaginator( +public class ResponsePaginator( pages: Pages, owner: UserBehavior? = null, timeoutSeconds: Long? = null, @@ -26,7 +26,7 @@ public class PublicResponsePaginator( bundle: String? = null, locale: Locale? = null, - public val interaction: PublicInteractionResponseBehavior, + public val interaction: InteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { /** Whether this paginator has been set up for the first time. **/ public var isSetup: Boolean = false @@ -43,7 +43,7 @@ public class PublicResponsePaginator( interaction.edit { embed { applyPage() } - with(this@PublicResponsePaginator.components) { + with(this@ResponsePaginator.components) { this@edit.applyToMessage() } } @@ -68,10 +68,10 @@ public class PublicResponsePaginator( /** Convenience function for creating an interaction button paginator from a paginator builder. **/ @Suppress("FunctionNaming") // Factory function -public fun PublicResponsePaginator( +public fun ResponsePaginator( builder: PaginatorBuilder, - interaction: PublicInteractionResponseBehavior -): PublicResponsePaginator = PublicResponsePaginator( + interaction: InteractionResponseBehavior +): ResponsePaginator = ResponsePaginator( pages = builder.pages, owner = builder.owner, timeoutSeconds = builder.timeoutSeconds, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt index 775d286f0b..fad72671f3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt @@ -10,12 +10,12 @@ public suspend inline fun PublicInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", builder: (PaginatorBuilder).() -> Unit -): PublicResponsePaginator { +): ResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return PublicResponsePaginator(pages, this) + return ResponsePaginator(pages, this) } /** Create a paginator that creates a follow-up message, and edits that. **/ @@ -39,10 +39,10 @@ public suspend inline fun EphemeralInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", builder: (PaginatorBuilder).() -> Unit -): EphemeralResponsePaginator { +): ResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return EphemeralResponsePaginator(pages, this) + return ResponsePaginator(pages, this) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt index 39127ee01b..4f68a2ab43 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt @@ -1,13 +1,16 @@ package com.kotlindiscord.kord.extensions.types -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator +import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit -import dev.kord.core.behavior.interaction.followUpEphemeral +import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.interaction.EphemeralFollowupMessage -import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder +import dev.kord.core.entity.interaction.PublicFollowupMessage +import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.InteractionResponseModifyBuilder import java.util.* /** Interface representing an ephemeral-only interaction action context. **/ @@ -22,14 +25,21 @@ public interface EphemeralInteractionContext { * **Note:** Calling this twice (or at all after [edit]) will result in a public followup! */ public suspend inline fun EphemeralInteractionContext.respond( - builder: EphemeralFollowupMessageCreateBuilder.() -> Unit -): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) + builder: FollowupMessageCreateBuilder.() -> Unit +): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) + +/** + * Respond to the current interaction with a public followup. + */ +public suspend inline fun EphemeralInteractionContext.respondPublic( + builder: FollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage = interactionResponse.followUp(builder = builder) /** * Edit the current interaction's response. */ public suspend inline fun EphemeralInteractionContext.edit( - builder: EphemeralInteractionResponseModifyBuilder.() -> Unit + builder: InteractionResponseModifyBuilder.() -> Unit ): Unit = interactionResponse.edit(builder) /** @@ -40,10 +50,23 @@ public suspend inline fun EphemeralInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): EphemeralResponsePaginator { +): ResponsePaginator { + val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) + + builder(pages) + + return ResponsePaginator(pages, interactionResponse) +} + +/** Create a paginator that creates a **public** follow-up message, and edits that. **/ +public suspend inline fun EphemeralInteractionContext.publicRespondingPaginator( + defaultGroup: String = "", + locale: Locale? = null, + builder: (PaginatorBuilder).() -> Unit +): PublicFollowUpPaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return EphemeralResponsePaginator(pages, interactionResponse) + return PublicFollowUpPaginator(pages, interactionResponse) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt index 1848fd9745..5022b2533d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt @@ -1,15 +1,16 @@ package com.kotlindiscord.kord.extensions.types import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator -import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.behavior.interaction.followUp -import dev.kord.core.entity.Message +import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder +import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder +import dev.kord.rest.builder.message.modify.InteractionResponseModifyBuilder import java.util.* /** Interface representing a public-only interaction action context. **/ @@ -20,27 +21,32 @@ public interface PublicInteractionContext { /** Respond to the current interaction with a public followup. **/ public suspend inline fun PublicInteractionContext.respond( - builder: PublicFollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage = interactionResponse.followUp(builder) + builder: FollowupMessageCreateBuilder.() -> Unit +): PublicFollowupMessage = interactionResponse.followUp(builder = builder) + +/** Respond to the current interaction with an ephemeral followup. **/ +public suspend inline fun PublicInteractionContext.respondEphemeral( + builder: FollowupMessageCreateBuilder.() -> Unit +): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) /** * Edit the current interaction's response. */ public suspend inline fun PublicInteractionContext.edit( - builder: PublicInteractionResponseModifyBuilder.() -> Unit -): Message = interactionResponse.edit(builder) + builder: InteractionResponseModifyBuilder.() -> Unit +): Unit = interactionResponse.edit(builder) /** Create a paginator that edits the original interaction. **/ public suspend inline fun PublicInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): PublicResponsePaginator { +): ResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return PublicResponsePaginator(pages, interactionResponse) + return ResponsePaginator(pages, interactionResponse) } /** Create a paginator that creates a follow-up message, and edits that. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt index a19031da5d..0b48b018ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt @@ -2,14 +2,22 @@ package com.kotlindiscord.kord.extensions.utils import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord +import dev.kord.core.behavior.interaction.InteractionResponseBehavior +import dev.kord.core.cache.data.toData +import dev.kord.core.entity.Message import dev.kord.core.entity.User +import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.event.Event import dev.kord.core.firstOrNull import dev.kord.core.live.LiveKordEntity import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.withTimeoutOrNull +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** Flow containing all [User] objects in the cache. **/ public val Kord.users: Flow @@ -57,3 +65,19 @@ public suspend inline fun LiveKordEntity.waitFor( } } } + +/** + * This is a hack to make things build with the current M6 release of Kord. It's missing from the release, + * and will be added later. + */ +@OptIn(ExperimentalContracts::class) +public suspend inline fun InteractionResponseBehavior.ephemeralFollowup( + builder: FollowupMessageCreateBuilder.() -> Unit +): EphemeralFollowupMessage { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + + val builder = FollowupMessageCreateBuilder(true).apply(builder) + val message = kord.rest.interaction.createFollowupMessage(applicationId, token, builder.toRequest()) + + return EphemeralFollowupMessage(Message(message.toData(), kord), applicationId, token, kord) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt index 354acf7e6c..fff9e35033 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Message.kt @@ -22,8 +22,6 @@ import dev.kord.rest.request.RestRequestException import io.ktor.http.* import kotlinx.coroutines.* import mu.KotlinLogging -import org.apache.commons.text.StringTokenizer -import org.apache.commons.text.matcher.StringMatcherFactory private val logger = KotlinLogging.logger {} @@ -171,29 +169,6 @@ public val MessageData.authorId: Snowflake public val MessageData.authorIsBot: Boolean get() = author.bot.discordBoolean -/** - * Takes a [Message] object and parses it using a [StringTokenizer]. - * - * This tokenizes a string, splitting it into an array of strings using whitespace as a - * delimiter, but supporting quoted tokens (strings between quotes are treated as individual - * arguments). - * - * This is used to create an array of arguments for a command's input. - * - * @param delimiters An array of delimiters to split with, if not just a space - * @param quotes An array of quote characters, if you need something other than just `'` and `"` - * - * @return An array of parsed arguments - */ -public fun Message.parse( - delimiters: CharArray = charArrayOf(' '), - quotes: CharArray = charArrayOf('\'', '"') -): Array = - StringTokenizer(content) - .setDelimiterMatcher(StringMatcherFactory.INSTANCE.charSetMatcher(delimiters.joinToString())) - .setQuoteMatcher(StringMatcherFactory.INSTANCE.charSetMatcher(quotes.joinToString())) - .tokenArray - /** * Respond to a message in the channel it was sent to, mentioning the author. * diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Users.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Users.kt index 7b20ceab04..4ada0dbe54 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Users.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Users.kt @@ -23,7 +23,7 @@ public val User.profileLink: String * The user's creation timestamp. */ public val User.createdAt: Instant - get() = this.id.timeStamp + get() = this.id.timestamp /** * Send a private message to a user, if they have their DMs enabled. diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/MemberDelta.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/MemberDelta.kt index c498140220..61e78c1317 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/MemberDelta.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/MemberDelta.kt @@ -2,8 +2,8 @@ package com.kotlindiscord.kord.extensions.utils.deltas import dev.kord.common.entity.UserFlags import dev.kord.common.entity.optional.Optional +import dev.kord.core.entity.Icon import dev.kord.core.entity.Member -import dev.kord.core.entity.User import kotlinx.datetime.Instant import kotlin.contracts.contract @@ -18,7 +18,7 @@ import kotlin.contracts.contract */ @Suppress("UndocumentedPublicProperty") public class MemberDelta( - avatar: Optional, + avatar: Optional, username: Optional, discriminator: Optional, flags: Optional, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/UserDelta.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/UserDelta.kt index 88694dd4cd..df7b1db7a3 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/UserDelta.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/deltas/UserDelta.kt @@ -2,6 +2,7 @@ package com.kotlindiscord.kord.extensions.utils.deltas import dev.kord.common.entity.UserFlags import dev.kord.common.entity.optional.Optional +import dev.kord.core.entity.Icon import dev.kord.core.entity.User import kotlin.contracts.contract @@ -16,7 +17,7 @@ import kotlin.contracts.contract */ @Suppress("UndocumentedPublicProperty") public open class UserDelta( - public val avatar: Optional, + public val avatar: Optional, public val username: Optional, public val discriminator: Optional, public val flags: Optional @@ -48,7 +49,7 @@ public open class UserDelta( old ?: return null return UserDelta( - if (old.avatar.url != new.avatar.url) Optional(new.avatar) else Optional.Missing(), + if (old.avatar?.url != new.avatar?.url) Optional(new.avatar) else Optional.Missing(), if (old.username != new.username) Optional(new.username) else Optional.Missing(), if (old.discriminator != new.discriminator) Optional(new.discriminator) else Optional.Missing(), if (old.publicFlags != new.publicFlags) Optional(new.publicFlags) else Optional.Missing() diff --git a/libs.versions.toml b/libs.versions.toml index dc2c43bd2c..b5e2a6902a 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -3,38 +3,39 @@ detekt = "1.17.1" # Note: Plugin versions must be updated in the settings.gradl dokka = "1.4.10.2" # Note: Plugin versions must be updated in the settings.gradle.kts too kotlin = "1.5.30" # Note: Plugin versions must be updated in the settings.gradle.kts too -commons-text = "1.9" commons-validator = "1.7" groovy = "3.0.8" icu4j = "69.1" junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" -#kord = "0.8.0-M4" -kord = "0.8.0-M5" +#kord = "0.8.0-M5" +kord = "0.8.0-M6" kotlinpoet = "1.8.0" ksp = "1.5.30-1.0.0-beta08" -ktor = "1.6.0" -kx-ser = "1.2.2" +ktor = "1.6.3" +kx-ser = "1.3.0" linkie = "1.0.88" logback = "1.2.3" -logging = "2.0.6" +logging = "2.0.11" sentry = "5.0.0" time4j-base = "5.8" time4j-tzdata = "5.0-2021a" [libraries] -commons-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" } commons-validator = { module = "commons-validator:commons-validator", version.ref = "commons-validator" } detekt = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" } junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } + koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-logger = { module = "io.insert-koin:koin-logger-slf4j", version.ref = "koin" } koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } + konf-core = { module = "com.uchuhimo:konf", version.ref = "konf" } konf-toml = { module = "com.uchuhimo:konf-toml", version.ref = "konf" } + kord = { module = "dev.kord:kord-core", version.ref = "kord" } kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } @@ -49,4 +50,4 @@ time4j-base = { module = "net.time4j:time4j-base", version.ref = "time4j-base" } time4j-tzdata = { module = "net.time4j:time4j-tzdata", version.ref = "time4j-tzdata" } [bundles] -commons = [ "commons-text", "commons-validator" ] +commons = [ "commons-validator" ] diff --git a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 5f3bc6a70a..7373ff47a2 100644 --- a/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/modules/java-time/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -6,18 +6,18 @@ import com.kotlindiscord.kord.extensions.utils.env import org.koin.core.logger.Level suspend fun main() { - val bot = ExtensibleBot(env("TOKEN")!!) { + val bot = ExtensibleBot(env("TOKEN")) { koinLogLevel = Level.DEBUG i18n { - localeResolver { guild, channel, user -> + localeResolver { _, _, user -> @Suppress("UnderscoresInNumericLiterals") when (user?.id?.value) { - 560515299388948500 -> SupportedLocales.FINNISH - 242043299022635020 -> SupportedLocales.FRENCH - 407110650217627658 -> SupportedLocales.FRENCH - 667552017434017794 -> SupportedLocales.CHINESE_SIMPLIFIED - 185461862878543872 -> SupportedLocales.GERMAN + 560515299388948500UL -> SupportedLocales.FINNISH + 242043299022635020UL -> SupportedLocales.FRENCH + 407110650217627658UL -> SupportedLocales.FRENCH + 667552017434017794UL -> SupportedLocales.CHINESE_SIMPLIFIED + 185461862878543872UL -> SupportedLocales.GERMAN else -> defaultLocale } diff --git a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt index 5716acf6c0..21a5601661 100644 --- a/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt +++ b/modules/time4j/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/Bot.kt @@ -5,7 +5,7 @@ import com.kotlindiscord.kord.extensions.utils.env import org.koin.core.logger.Level suspend fun main() { - val bot = ExtensibleBot(env("TOKEN")!!) { + val bot = ExtensibleBot(env("TOKEN")) { koinLogLevel = Level.DEBUG chatCommands { diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt index 37e736bf79..1595de1631 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -3,22 +3,17 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.types import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI -import com.kotlindiscord.kord.extensions.pagination.BaseButtonPaginator -import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator -import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.* -import dev.kord.core.entity.Message import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.core.event.interaction.ApplicationInteractionCreateEvent -import dev.kord.rest.builder.message.create.EphemeralFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.EphemeralInteractionResponseCreateBuilder -import dev.kord.rest.builder.message.create.PublicFollowupMessageCreateBuilder -import dev.kord.rest.builder.message.create.PublicInteractionResponseCreateBuilder -import dev.kord.rest.builder.message.modify.EphemeralInteractionResponseModifyBuilder -import dev.kord.rest.builder.message.modify.PublicInteractionResponseModifyBuilder +import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder +import dev.kord.rest.builder.message.create.InteractionResponseCreateBuilder +import dev.kord.rest.builder.message.modify.InteractionResponseModifyBuilder import java.util.* /** Interface representing a generic, unsafe interaction action context. **/ @@ -34,7 +29,7 @@ public interface UnsafeInteractionContext { /** Send an ephemeral ack, if the interaction hasn't been acknowledged yet. **/ @UnsafeAPI public suspend fun UnsafeInteractionContext.ackEphemeral( - builder: (suspend EphemeralInteractionResponseCreateBuilder.() -> Unit)? = null + builder: (suspend InteractionResponseCreateBuilder.() -> Unit)? = null ): EphemeralInteractionResponseBehavior { if (interactionResponse != null) { error("The interaction has already been acknowledged.") @@ -52,7 +47,7 @@ public suspend fun UnsafeInteractionContext.ackEphemeral( /** Send a public ack, if the interaction hasn't been acknowledged yet. **/ @UnsafeAPI public suspend fun UnsafeInteractionContext.ackPublic( - builder: (suspend PublicInteractionResponseCreateBuilder.() -> Unit)? = null + builder: (suspend InteractionResponseCreateBuilder.() -> Unit)? = null ): PublicInteractionResponseBehavior { if (interactionResponse != null) { error("The interaction has already been acknowledged.") @@ -70,28 +65,26 @@ public suspend fun UnsafeInteractionContext.ackPublic( /** Respond to the current interaction with an ephemeral followup, or throw if it isn't ephemeral. **/ @UnsafeAPI public suspend inline fun UnsafeInteractionContext.respondEphemeral( - builder: EphemeralFollowupMessageCreateBuilder.() -> Unit + builder: FollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage { return when (val interaction = interactionResponse) { - is EphemeralInteractionResponseBehavior -> interaction.followUpEphemeral(builder) - is PublicInteractionResponseBehavior -> error("Initial interaction response is not public.") + is InteractionResponseBehavior -> interaction.ephemeralFollowup(builder) null -> error("Acknowledge the interaction before trying to follow-up.") - else -> error("Unsupported initial interaction response type - please report this.") + else -> error("Unsupported initial interaction response type $interaction - please report this.") } } /** Respond to the current interaction with a public followup. **/ @UnsafeAPI public suspend inline fun UnsafeInteractionContext.respondPublic( - builder: PublicFollowupMessageCreateBuilder.() -> Unit + builder: FollowupMessageCreateBuilder.() -> Unit ): PublicFollowupMessage { return when (val interaction = interactionResponse) { - is PublicInteractionResponseBehavior -> interaction.followUp(builder) - is EphemeralInteractionResponseBehavior -> interaction.followUpPublic(builder) + is InteractionResponseBehavior -> interaction.followUp { builder() } null -> error("Acknowledge the interaction before trying to follow-up.") - else -> error("Unsupported initial interaction response type - please report this.") + else -> error("Unsupported initial interaction response type $interaction - please report this.") } } @@ -100,30 +93,14 @@ public suspend inline fun UnsafeInteractionContext.respondPublic( */ @Suppress("UseIfInsteadOfWhen") @UnsafeAPI -public suspend inline fun UnsafeInteractionContext.editPublic( - builder: PublicInteractionResponseModifyBuilder.() -> Unit -): Message { - return when (val interaction = interactionResponse) { - is PublicInteractionResponseBehavior -> interaction.edit(builder) - - null -> error("Acknowledge the interaction before trying to edit it.") - else -> error("Initial interaction response was not public.") - } -} - -/** - * Edit the current interaction's response, or throw if it isn't ephemeral. - */ -@Suppress("UseIfInsteadOfWhen") -@UnsafeAPI -public suspend inline fun UnsafeInteractionContext.editEphemeral( - builder: EphemeralInteractionResponseModifyBuilder.() -> Unit +public suspend inline fun UnsafeInteractionContext.edit( + builder: InteractionResponseModifyBuilder.() -> Unit ) { - when (val interaction = interactionResponse) { - is EphemeralInteractionResponseBehavior -> interaction.edit(builder) + return when (val interaction = interactionResponse) { + is InteractionResponseBehavior -> interaction.edit(builder) null -> error("Acknowledge the interaction before trying to edit it.") - else -> error("Initial interaction response was not ephemeral.") + else -> error("Unsupported initial interaction response type $interaction - please report this.") } } @@ -133,14 +110,13 @@ public suspend inline fun UnsafeInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): BaseButtonPaginator { +): ResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) return when (val interaction = interactionResponse) { - is PublicInteractionResponseBehavior -> PublicResponsePaginator(pages, interaction) - is EphemeralInteractionResponseBehavior -> EphemeralResponsePaginator(pages, interaction) + is InteractionResponseBehavior -> ResponsePaginator(pages, interaction) null -> error("Acknowledge the interaction before trying to edit it.") else -> error("Unsupported initial interaction response type - please report this.") @@ -150,19 +126,19 @@ public suspend inline fun UnsafeInteractionContext.editingPaginator( /** Create a paginator that creates a follow-up message, and edits that. **/ @Suppress("UseIfInsteadOfWhen") @UnsafeAPI -public suspend inline fun UnsafeInteractionContext.respondingPaginator( +public suspend inline fun UnsafeInteractionContext.publicRespondingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): BaseButtonPaginator { +): PublicFollowUpPaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) return when (val interaction = interactionResponse) { - is PublicInteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) + is InteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) null -> error("Acknowledge the interaction before trying to follow-up.") - else -> error("Initial interaction response was not public.") + else -> error("Unsupported initial interaction response type $interaction - please report this.") } } From d18cb5c7a21c37f9acc9076e6f054169d36f1abc Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 7 Oct 2021 22:24:52 +0100 Subject: [PATCH 125/131] Bot setup should be before extension load --- .../kord/extensions/builders/ExtensibleBotBuilder.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index bf9b06c86f..6faa527f80 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -344,15 +344,16 @@ public open class ExtensibleBotBuilder { loadModule { single { bot } bind ExtensibleBot::class } hooksBuilder.runCreated(bot) + + bot.setup() + + hooksBuilder.runSetup(bot) hooksBuilder.runBeforeExtensionsAdded(bot) extensionsBuilder.extensions.forEach { bot.addExtension(it) } hooksBuilder.runAfterExtensionsAdded(bot) - bot.setup() - hooksBuilder.runSetup(bot) - return bot } From 8f4e87afcbb36fabf9ea74825b7c72d82a4ad003 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Thu, 7 Oct 2021 22:35:19 +0100 Subject: [PATCH 126/131] Register listeners later --- .../kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt index 82e6f603a1..7ad781da63 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/ExtensibleBot.kt @@ -97,13 +97,13 @@ public open class ExtensibleBot(public val settings: ExtensibleBotBuilder, priva } } - registerListeners() addDefaultExtensions() } /** Start up the bot and log into Discord. **/ public open suspend fun start() { settings.hooksBuilder.runBeforeStart(this) + registerListeners() getKoin().get().login { this.presence(settings.presenceBuilder) From a65013472811ce67ca62f727128bd97f049cc35f Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Fri, 8 Oct 2021 11:22:18 +0100 Subject: [PATCH 127/131] Phishing: API v2 --- extra-modules/extra-phishing/build.gradle.kts | 2 ++ .../modules/extra/phishing/DomainChange.kt | 26 +++++++++++++++++++ .../modules/extra/phishing/PhishingApi.kt | 23 ++++++++-------- .../extra/phishing/PhishingExtension.kt | 8 +++++- 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DomainChange.kt diff --git a/extra-modules/extra-phishing/build.gradle.kts b/extra-modules/extra-phishing/build.gradle.kts index 1ea8eedb60..d9eb8ff125 100644 --- a/extra-modules/extra-phishing/build.gradle.kts +++ b/extra-modules/extra-phishing/build.gradle.kts @@ -3,6 +3,8 @@ plugins { `published-module` `dokka-module` `disable-explicit-api-mode` + + kotlin("plugin.serialization") } repositories { diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DomainChange.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DomainChange.kt new file mode 100644 index 0000000000..622c9fe2d2 --- /dev/null +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/DomainChange.kt @@ -0,0 +1,26 @@ +package com.kotlindiscord.kord.extensions.modules.extra.phishing + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Data class representing a Sinking Yachts domain change object. + * + * @property type Domain change type - add or delete + * @property domains Set of domains that this change concerns + */ +@Serializable +data class DomainChange( + val type: DomainChangeType, + val domains: Set +) + +/** Enum representing domain change types. **/ +@Serializable +enum class DomainChangeType { + @SerialName("add") + Add, + + @SerialName("delete") + Delete +} diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt index d2aa276d68..27e654210b 100644 --- a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingApi.kt @@ -1,21 +1,20 @@ package com.kotlindiscord.kord.extensions.modules.extra.phishing -import io.ktor.client.HttpClient -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.logging.LogLevel -import io.ktor.client.features.logging.Logging -import io.ktor.client.request.get -import io.ktor.client.request.header - -internal const val ALL_PATH = "https://phish.sinking.yachts/all" -internal const val CHECK_PATH = "https://phish.sinking.yachts/check/%" -internal const val RECENT_PATH = "https://phish.sinking.yachts/recent/%" -internal const val SIZE_PATH = "https://phish.sinking.yachts/dbsize" +import io.ktor.client.* +import io.ktor.client.features.json.* +import io.ktor.client.features.logging.* +import io.ktor.client.request.* + +internal const val ALL_PATH = "https://phish.sinking.yachts/v2/all" +internal const val CHECK_PATH = "https://phish.sinking.yachts/v2/check/%" +internal const val RECENT_PATH = "https://phish.sinking.yachts/v2/recent/%" +internal const val SIZE_PATH = "https://phish.sinking.yachts/v2/dbsize" /** Implementation of the Sinking Yachts phishing domain API. **/ class PhishingApi(internal val appName: String) { internal val client = HttpClient { install(JsonFeature) + install(Logging) { level = LogLevel.INFO } @@ -34,7 +33,7 @@ class PhishingApi(internal val appName: String) { get(CHECK_PATH.replace("%", domain)) /** Get all new phishing domains added in the previous [seconds] seconds. **/ - suspend fun getRecentDomains(seconds: Long): Set = + suspend fun getRecentDomains(seconds: Long): List = get(RECENT_PATH.replace("%", seconds.toString())) /** Get the total number of phishing domains that the API knows about. **/ diff --git a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt index 9469164048..4d95810a0e 100644 --- a/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt +++ b/extra-modules/extra-phishing/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/extra/phishing/PhishingExtension.kt @@ -284,7 +284,13 @@ class PhishingExtension(private val settings: ExtPhishingBuilder) : Extension() logger.trace { "Updating domains..." } // An extra 30 seconds for safety - domainCache.addAll(api.getRecentDomains(settings.updateDelay.inWholeSeconds + 30)) + api.getRecentDomains(settings.updateDelay.inWholeSeconds + 30).forEach { + when (it.type) { + DomainChangeType.Add -> domainCache.addAll(it.domains) + DomainChangeType.Delete -> domainCache.removeAll(it.domains) + } + } + checkTask?.restart() // Off we go again } From d064fa4c58f78b9c87c6f3c81c22da2755c5f621 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Mon, 11 Oct 2021 11:15:42 +0100 Subject: [PATCH 128/131] Switch to Kord snapshot: 0.8.x --- .../converters/impl/NumberChoiceConverter.kt | 2 +- .../pagination/EphemeralResponsePaginator.kt | 82 +++++++++++++++++++ ...aginator.kt => PublicResponsePaginator.kt} | 14 ++-- .../kord/extensions/pagination/_Functions.kt | 8 +- .../types/EphemeralInteractionContext.kt | 18 ++-- .../types/PublicInteractionContext.kt | 12 +-- .../kord/extensions/utils/_Kord.kt | 24 ------ .../kord/extensions/test/bot/TestExtension.kt | 4 +- libs.versions.toml | 4 +- .../unsafe/types/UnsafeInteractionContext.kt | 18 ++-- 10 files changed, 122 insertions(+), 64 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt rename kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/{ResponsePaginator.kt => PublicResponsePaginator.kt} (85%) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt index ea5eb9d2cd..c8cd3b7df2 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/application/slash/converters/impl/NumberChoiceConverter.kt @@ -67,7 +67,7 @@ class NumberChoiceConverter( IntChoiceBuilder(arg.displayName, arg.description).apply { required = true - this@NumberChoiceConverter.choices.forEach { choice(it.key, it.value.toInt()) } + this@NumberChoiceConverter.choices.forEach { choice(it.key, it.value) } } override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt new file mode 100644 index 0000000000..b1832f3234 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/EphemeralResponsePaginator.kt @@ -0,0 +1,82 @@ +@file:OptIn(KordPreview::class) + +package com.kotlindiscord.kord.extensions.pagination + +import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder +import com.kotlindiscord.kord.extensions.pagination.pages.Pages +import dev.kord.common.annotation.KordPreview +import dev.kord.core.behavior.UserBehavior +import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior +import dev.kord.core.behavior.interaction.edit +import dev.kord.core.entity.ReactionEmoji +import dev.kord.rest.builder.message.modify.embed +import java.util.* + +/** + * Class representing a button-based paginator that operates by editing the given ephemeral interaction response. + * + * @param interaction Interaction response behaviour to work with. + */ +public class EphemeralResponsePaginator( + pages: Pages, + owner: UserBehavior? = null, + timeoutSeconds: Long? = null, + switchEmoji: ReactionEmoji = if (pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, + bundle: String? = null, + locale: Locale? = null, + + public val interaction: EphemeralInteractionResponseBehavior, +) : BaseButtonPaginator(pages, owner, timeoutSeconds, true, switchEmoji, bundle, locale) { + /** Whether this paginator has been set up for the first time. **/ + public var isSetup: Boolean = false + + override suspend fun send() { + if (!isSetup) { + isSetup = true + + setup() + } else { + updateButtons() + } + + interaction.edit { + embed { applyPage() } + + with(this@EphemeralResponsePaginator.components) { + this@edit.applyToMessage() + } + } + } + + override suspend fun destroy() { + if (!active) { + return + } + + active = false + + interaction.edit { + embed { applyPage() } + + this.components = mutableListOf() + } + + super.destroy() + } +} + +/** Convenience function for creating an interaction button paginator from a paginator builder. **/ +@Suppress("FunctionNaming") // Factory function +public fun EphemeralResponsePaginator( + builder: PaginatorBuilder, + interaction: EphemeralInteractionResponseBehavior +): EphemeralResponsePaginator = EphemeralResponsePaginator( + pages = builder.pages, + owner = builder.owner, + timeoutSeconds = builder.timeoutSeconds, + bundle = builder.bundle, + locale = builder.locale, + interaction = interaction, + + switchEmoji = builder.switchEmoji ?: if (builder.pages.groups.size == 2) EXPAND_EMOJI else SWITCH_EMOJI, +) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt similarity index 85% rename from kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt rename to kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt index ebe48816f8..c0255b927d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/ResponsePaginator.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/PublicResponsePaginator.kt @@ -6,7 +6,7 @@ import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import com.kotlindiscord.kord.extensions.pagination.pages.Pages import dev.kord.common.annotation.KordPreview import dev.kord.core.behavior.UserBehavior -import dev.kord.core.behavior.interaction.InteractionResponseBehavior +import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit import dev.kord.core.entity.ReactionEmoji import dev.kord.rest.builder.message.modify.embed @@ -17,7 +17,7 @@ import java.util.* * * @param interaction Interaction response behaviour to work with. */ -public class ResponsePaginator( +public class PublicResponsePaginator( pages: Pages, owner: UserBehavior? = null, timeoutSeconds: Long? = null, @@ -26,7 +26,7 @@ public class ResponsePaginator( bundle: String? = null, locale: Locale? = null, - public val interaction: InteractionResponseBehavior, + public val interaction: PublicInteractionResponseBehavior, ) : BaseButtonPaginator(pages, owner, timeoutSeconds, keepEmbed, switchEmoji, bundle, locale) { /** Whether this paginator has been set up for the first time. **/ public var isSetup: Boolean = false @@ -43,7 +43,7 @@ public class ResponsePaginator( interaction.edit { embed { applyPage() } - with(this@ResponsePaginator.components) { + with(this@PublicResponsePaginator.components) { this@edit.applyToMessage() } } @@ -68,10 +68,10 @@ public class ResponsePaginator( /** Convenience function for creating an interaction button paginator from a paginator builder. **/ @Suppress("FunctionNaming") // Factory function -public fun ResponsePaginator( +public fun PublicResponsePaginator( builder: PaginatorBuilder, - interaction: InteractionResponseBehavior -): ResponsePaginator = ResponsePaginator( + interaction: PublicInteractionResponseBehavior +): PublicResponsePaginator = PublicResponsePaginator( pages = builder.pages, owner = builder.owner, timeoutSeconds = builder.timeoutSeconds, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt index fad72671f3..775d286f0b 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/pagination/_Functions.kt @@ -10,12 +10,12 @@ public suspend inline fun PublicInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", builder: (PaginatorBuilder).() -> Unit -): ResponsePaginator { +): PublicResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return ResponsePaginator(pages, this) + return PublicResponsePaginator(pages, this) } /** Create a paginator that creates a follow-up message, and edits that. **/ @@ -39,10 +39,10 @@ public suspend inline fun EphemeralInteractionResponseBehavior.editingPaginator( locale: Locale? = null, defaultGroup: String = "", builder: (PaginatorBuilder).() -> Unit -): ResponsePaginator { +): EphemeralResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return ResponsePaginator(pages, this) + return EphemeralResponsePaginator(pages, this) } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt index 4f68a2ab43..01cdb36ba0 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt @@ -1,11 +1,11 @@ package com.kotlindiscord.kord.extensions.types +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator -import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder -import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit +import dev.kord.core.behavior.interaction.ephemeralFollowup import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage @@ -28,12 +28,10 @@ public suspend inline fun EphemeralInteractionContext.respond( builder: FollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) -/** - * Respond to the current interaction with a public followup. - */ -public suspend inline fun EphemeralInteractionContext.respondPublic( +/** Respond to the current interaction with a public followup. **/ +public suspend inline fun PublicInteractionContext.respondPublic( builder: FollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage = interactionResponse.followUp(builder = builder) +): PublicFollowupMessage = interactionResponse.followUp(builder) /** * Edit the current interaction's response. @@ -50,15 +48,15 @@ public suspend inline fun EphemeralInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): ResponsePaginator { +): EphemeralResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return ResponsePaginator(pages, interactionResponse) + return EphemeralResponsePaginator(pages, interactionResponse) } -/** Create a paginator that creates a **public** follow-up message, and edits that. **/ +/** Create a paginator that creates a follow-up message, and edits that. **/ public suspend inline fun EphemeralInteractionContext.publicRespondingPaginator( defaultGroup: String = "", locale: Locale? = null, diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt index 5022b2533d..da66729ffa 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt @@ -1,11 +1,11 @@ package com.kotlindiscord.kord.extensions.types import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator -import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder -import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit +import dev.kord.core.behavior.interaction.ephemeralFollowup import dev.kord.core.behavior.interaction.followUp import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage @@ -22,9 +22,9 @@ public interface PublicInteractionContext { /** Respond to the current interaction with a public followup. **/ public suspend inline fun PublicInteractionContext.respond( builder: FollowupMessageCreateBuilder.() -> Unit -): PublicFollowupMessage = interactionResponse.followUp(builder = builder) +): PublicFollowupMessage = interactionResponse.followUp(builder) -/** Respond to the current interaction with an ephemeral followup. **/ +/** Respond to the current interaction with a public followup. **/ public suspend inline fun PublicInteractionContext.respondEphemeral( builder: FollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) @@ -41,12 +41,12 @@ public suspend inline fun PublicInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): ResponsePaginator { +): PublicResponsePaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) - return ResponsePaginator(pages, interactionResponse) + return PublicResponsePaginator(pages, interactionResponse) } /** Create a paginator that creates a follow-up message, and edits that. **/ diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt index 0b48b018ec..a19031da5d 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/utils/_Kord.kt @@ -2,22 +2,14 @@ package com.kotlindiscord.kord.extensions.utils import dev.kord.common.annotation.KordPreview import dev.kord.core.Kord -import dev.kord.core.behavior.interaction.InteractionResponseBehavior -import dev.kord.core.cache.data.toData -import dev.kord.core.entity.Message import dev.kord.core.entity.User -import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.event.Event import dev.kord.core.firstOrNull import dev.kord.core.live.LiveKordEntity import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.withTimeoutOrNull -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** Flow containing all [User] objects in the cache. **/ public val Kord.users: Flow @@ -65,19 +57,3 @@ public suspend inline fun LiveKordEntity.waitFor( } } } - -/** - * This is a hack to make things build with the current M6 release of Kord. It's missing from the release, - * and will be added later. - */ -@OptIn(ExperimentalContracts::class) -public suspend inline fun InteractionResponseBehavior.ephemeralFollowup( - builder: FollowupMessageCreateBuilder.() -> Unit -): EphemeralFollowupMessage { - contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } - - val builder = FollowupMessageCreateBuilder(true).apply(builder) - val message = kord.rest.interaction.createFollowupMessage(applicationId, token, builder.toRequest()) - - return EphemeralFollowupMessage(Message(message.toData(), kord), applicationId, token, kord) -} diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt index 45548bf8f7..6e0c35c548 100644 --- a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/test/bot/TestExtension.kt @@ -103,7 +103,7 @@ class TestExtension : Extension() { check { failIf("This message command only supports non-webhook, non-interaction messages.") { - event.interaction.messages?.values?.firstOrNull()?.author == null + event.interaction.messages.values.firstOrNull()?.author == null } } @@ -125,7 +125,7 @@ class TestExtension : Extension() { check { failIf("That's me, you can't make me ping myself!") { - event.interaction.users?.values?.firstOrNull()?.id == kord.selfId + event.interaction.users.values.firstOrNull()?.id == kord.selfId } } diff --git a/libs.versions.toml b/libs.versions.toml index b5e2a6902a..f514e9bc1a 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -9,8 +9,8 @@ icu4j = "69.1" junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" -#kord = "0.8.0-M5" -kord = "0.8.0-M6" +#kord = "0.8.0-M6" +kord = "0.8.x-SNAPSHOT" kotlinpoet = "1.8.0" ksp = "1.5.30-1.0.0-beta08" ktor = "1.6.3" diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt index 1595de1631..84f0bb5a89 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -3,10 +3,11 @@ package com.kotlindiscord.kord.extensions.modules.unsafe.types import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.pagination.BaseButtonPaginator +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator -import com.kotlindiscord.kord.extensions.pagination.ResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder -import com.kotlindiscord.kord.extensions.utils.ephemeralFollowup import dev.kord.core.behavior.interaction.* import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage @@ -110,13 +111,14 @@ public suspend inline fun UnsafeInteractionContext.editingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): ResponsePaginator { +): BaseButtonPaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) return when (val interaction = interactionResponse) { - is InteractionResponseBehavior -> ResponsePaginator(pages, interaction) + is PublicInteractionResponseBehavior -> PublicResponsePaginator(pages, interaction) + is EphemeralInteractionResponseBehavior -> EphemeralResponsePaginator(pages, interaction) null -> error("Acknowledge the interaction before trying to edit it.") else -> error("Unsupported initial interaction response type - please report this.") @@ -126,19 +128,19 @@ public suspend inline fun UnsafeInteractionContext.editingPaginator( /** Create a paginator that creates a follow-up message, and edits that. **/ @Suppress("UseIfInsteadOfWhen") @UnsafeAPI -public suspend inline fun UnsafeInteractionContext.publicRespondingPaginator( +public suspend inline fun UnsafeInteractionContext.respondingPaginator( defaultGroup: String = "", locale: Locale? = null, builder: (PaginatorBuilder).() -> Unit -): PublicFollowUpPaginator { +): BaseButtonPaginator { val pages = PaginatorBuilder(locale = locale, defaultGroup = defaultGroup) builder(pages) return when (val interaction = interactionResponse) { - is InteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) + is PublicInteractionResponseBehavior -> PublicFollowUpPaginator(pages, interaction) null -> error("Acknowledge the interaction before trying to follow-up.") - else -> error("Unsupported initial interaction response type $interaction - please report this.") + else -> error("Initial interaction response was not public.") } } From 099874f5f264595f2a559d95ee304bf332957a11 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 12 Oct 2021 11:09:48 +0100 Subject: [PATCH 129/131] Update to Kord M7 --- .../kord/extensions/types/EphemeralInteractionContext.kt | 4 ++-- .../kord/extensions/types/PublicInteractionContext.kt | 4 ++-- libs.versions.toml | 4 ++-- .../modules/unsafe/types/UnsafeInteractionContext.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt index 01cdb36ba0..f1342a970e 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/EphemeralInteractionContext.kt @@ -5,8 +5,8 @@ import com.kotlindiscord.kord.extensions.pagination.PublicFollowUpPaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import dev.kord.core.behavior.interaction.EphemeralInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit -import dev.kord.core.behavior.interaction.ephemeralFollowup import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.behavior.interaction.followUpEphemeral import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder @@ -26,7 +26,7 @@ public interface EphemeralInteractionContext { */ public suspend inline fun EphemeralInteractionContext.respond( builder: FollowupMessageCreateBuilder.() -> Unit -): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) +): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) /** Respond to the current interaction with a public followup. **/ public suspend inline fun PublicInteractionContext.respondPublic( diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt index da66729ffa..6f97dd08bf 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/types/PublicInteractionContext.kt @@ -5,8 +5,8 @@ import com.kotlindiscord.kord.extensions.pagination.PublicResponsePaginator import com.kotlindiscord.kord.extensions.pagination.builders.PaginatorBuilder import dev.kord.core.behavior.interaction.PublicInteractionResponseBehavior import dev.kord.core.behavior.interaction.edit -import dev.kord.core.behavior.interaction.ephemeralFollowup import dev.kord.core.behavior.interaction.followUp +import dev.kord.core.behavior.interaction.followUpEphemeral import dev.kord.core.entity.interaction.EphemeralFollowupMessage import dev.kord.core.entity.interaction.PublicFollowupMessage import dev.kord.rest.builder.message.create.FollowupMessageCreateBuilder @@ -27,7 +27,7 @@ public suspend inline fun PublicInteractionContext.respond( /** Respond to the current interaction with a public followup. **/ public suspend inline fun PublicInteractionContext.respondEphemeral( builder: FollowupMessageCreateBuilder.() -> Unit -): EphemeralFollowupMessage = interactionResponse.ephemeralFollowup(builder) +): EphemeralFollowupMessage = interactionResponse.followUpEphemeral(builder) /** * Edit the current interaction's response. diff --git a/libs.versions.toml b/libs.versions.toml index f514e9bc1a..703c4321db 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -9,8 +9,8 @@ icu4j = "69.1" junit = "5.6.2" koin = "3.0.2" konf = "0.23.0" -#kord = "0.8.0-M6" -kord = "0.8.x-SNAPSHOT" +#kord = "0.8.x-SNAPSHOT" +kord = "0.8.0-M7" kotlinpoet = "1.8.0" ksp = "1.5.30-1.0.0-beta08" ktor = "1.6.3" diff --git a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt index 84f0bb5a89..d32b5be24c 100644 --- a/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt +++ b/modules/unsafe/src/main/kotlin/com/kotlindiscord/kord/extensions/modules/unsafe/types/UnsafeInteractionContext.kt @@ -69,7 +69,7 @@ public suspend inline fun UnsafeInteractionContext.respondEphemeral( builder: FollowupMessageCreateBuilder.() -> Unit ): EphemeralFollowupMessage { return when (val interaction = interactionResponse) { - is InteractionResponseBehavior -> interaction.ephemeralFollowup(builder) + is InteractionResponseBehavior -> interaction.followUpEphemeral(builder) null -> error("Acknowledge the interaction before trying to follow-up.") else -> error("Unsupported initial interaction response type $interaction - please report this.") From ff578b4f1b0843513a0428cc43166c057278bd04 Mon Sep 17 00:00:00 2001 From: AppleTheGolden Date: Tue, 12 Oct 2021 12:36:07 +0200 Subject: [PATCH 130/131] Add converter for discord-formatted timestamps (#102) --- .../impl/DurationCoalescingConverter.kt | 40 +++++--- .../converters/impl/DurationConverter.kt | 12 ++- .../converters/impl/TimestampConverter.kt | 92 +++++++++++++++++++ .../kord/extensions/time/TimestampType.kt | 19 ++++ .../translations/kordex/strings.properties | 14 +-- .../converters/impl/TimestampConverterTest.kt | 39 ++++++++ 6 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverter.kt create mode 100644 kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverterTest.kt diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt index cc0d884830..37afd150c9 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationCoalescingConverter.kt @@ -18,6 +18,7 @@ import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.datetime.* import mu.KotlinLogging +import kotlin.time.ExperimentalTime /** * Argument converter for Kotlin [DateTimePeriod] arguments. You can apply these to an `Instant` using `plus` and a @@ -37,7 +38,7 @@ import mu.KotlinLogging "shouldThrow: Boolean = false" ], ) -@OptIn(KordPreview::class) +@OptIn(KordPreview::class, ExperimentalTime::class) public class DurationCoalescingConverter( public val longHelp: Boolean = true, public val positiveOnly: Boolean = true, @@ -48,10 +49,23 @@ public class DurationCoalescingConverter( private val logger = KotlinLogging.logger {} override suspend fun parse(parser: StringParser?, context: CommandContext, named: List?): Int { - val durations: MutableList = mutableListOf() + // Check if it's a discord-formatted timestamp first + val timestamp = + (named?.getOrNull(0) ?: parser?.peekNext()?.data)?.let { TimestampConverter.parseFromString(it) } + if (timestamp != null) { + val result = (timestamp.instant - Clock.System.now()).toDateTimePeriod() + + checkPositive(context, result, positiveOnly) + + this.parsed = result + + return 1 + } + + val durations = mutableListOf() val ignoredWords: List = context.translate("utils.durations.ignoredWords").split(",") - var skipNext: Boolean = false + var skipNext = false val args: List = named ?: parser?.run { val tokens: MutableList = mutableListOf() @@ -121,14 +135,7 @@ public class DurationCoalescingConverter( context.getLocale() ) - if (positiveOnly) { - val now: Instant = Clock.System.now() - val applied: Instant = now.plus(result, TimeZone.UTC) - - if (now > applied) { - throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) - } - } + checkPositive(context, result, positiveOnly) parsed = result } catch (e: InvalidTimeUnitException) { @@ -163,6 +170,17 @@ public class DurationCoalescingConverter( logger.debug(e) { "Error thrown during duration parsing" } } + private suspend inline fun checkPositive(context: CommandContext, result: DateTimePeriod, positiveOnly: Boolean) { + if (positiveOnly) { + val now: Instant = Clock.System.now() + val applied: Instant = now.plus(result, TimeZone.UTC) + + if (now > applied) { + throw DiscordRelayedException(context.translate("converters.duration.error.positiveOnly")) + } + } + } + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt index 259683f261..9a6ef6902a 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/DurationConverter.kt @@ -16,10 +16,12 @@ import dev.kord.core.entity.interaction.OptionValue import dev.kord.rest.builder.interaction.OptionsBuilder import dev.kord.rest.builder.interaction.StringChoiceBuilder import kotlinx.datetime.* +import kotlin.time.ExperimentalTime /** * Argument converter for Kotlin [DateTimePeriod] arguments. You can apply these to an `Instant` using `plus` and a * timezone. + * Also accepts discord-formatted timestamps, in which case the DateTimePeriod will be the time until the timestamp. * * @param longHelp Whether to send the user a long help message with specific information on how to specify durations. * @param positiveOnly Whether a positive duration is required - `true` by default. @@ -34,7 +36,7 @@ import kotlinx.datetime.* "positiveOnly: Boolean = true" ], ) -@OptIn(KordPreview::class) +@OptIn(KordPreview::class, ExperimentalTime::class) public class DurationConverter( public val longHelp: Boolean = true, public val positiveOnly: Boolean = true, @@ -46,7 +48,13 @@ public class DurationConverter( val arg: String = named ?: parser?.parseNext()?.data ?: return false try { - val result: DateTimePeriod = DurationParser.parse(arg, context.getLocale()) + // Check if it's a discord-formatted timestamp first + val timestamp = TimestampConverter.parseFromString(arg) + val result: DateTimePeriod = if (timestamp == null) { + DurationParser.parse(arg, context.getLocale()) + } else { + (timestamp.instant - Clock.System.now()).toDateTimePeriod() + } if (positiveOnly) { val now: Instant = Clock.System.now() diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverter.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverter.kt new file mode 100644 index 0000000000..d22292bd29 --- /dev/null +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverter.kt @@ -0,0 +1,92 @@ +package com.kotlindiscord.kord.extensions.commands.converters.impl + +import com.kotlindiscord.kord.extensions.DiscordRelayedException +import com.kotlindiscord.kord.extensions.commands.Argument +import com.kotlindiscord.kord.extensions.commands.CommandContext +import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter +import com.kotlindiscord.kord.extensions.commands.converters.Validator +import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter +import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType +import com.kotlindiscord.kord.extensions.parser.StringParser +import com.kotlindiscord.kord.extensions.time.TimestampType +import com.kotlindiscord.kord.extensions.time.toDiscord +import dev.kord.common.annotation.KordPreview +import dev.kord.core.entity.interaction.OptionValue +import dev.kord.rest.builder.interaction.OptionsBuilder +import dev.kord.rest.builder.interaction.StringChoiceBuilder +import kotlinx.datetime.Instant + +private const val TIMESTAMP_PREFIX = " = null +) : SingleConverter() { + override val signatureTypeString: String = "converters.timestamp.signatureType" + + override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { + val arg: String = named ?: parser?.parseNext()?.data ?: return false + this.parsed = parseFromString(arg) ?: throw DiscordRelayedException( + context.translate( + "converters.timestamp.error.invalid", + replacements = arrayOf(arg) + ) + ) + + return true + } + + override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = + StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } + + override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { + val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false + this.parsed = parseFromString(optionValue) ?: throw DiscordRelayedException( + context.translate( + "converters.timestamp.error.invalid", + replacements = arrayOf(optionValue) + ) + ) + + return true + } + + internal companion object { + internal fun parseFromString(string: String): FormattedTimestamp? { + if (string.startsWith(TIMESTAMP_PREFIX) && string.endsWith(TIMESTAMP_SUFFIX)) { + val inner = string.removeSurrounding(TIMESTAMP_PREFIX, TIMESTAMP_SUFFIX).split(":") + val epochSeconds = inner.getOrNull(0) + val format = inner.getOrNull(1) + + return FormattedTimestamp( + Instant.fromEpochSeconds(epochSeconds?.toLongOrNull() ?: return null), + TimestampType.fromFormatSpecifier(format) ?: return null + ) + } else { + return null + } + } + } +} + +/** + * Container class for a timestamp and format, as expected by Discord. + * + * @param instant The timestamp this represents + * @param format Which format to display the timestamp in + */ +public data class FormattedTimestamp(val instant: Instant, val format: TimestampType) { + /** + * Format the timestamp using the format into Discord's special format. + */ + public fun toDiscord(): String = instant.toDiscord(format) +} diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/time/TimestampType.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/time/TimestampType.kt index f43e60764c..2202f18091 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/time/TimestampType.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/time/TimestampType.kt @@ -32,4 +32,23 @@ public sealed class TimestampType(public val string: String?) { /** Format the given [Long] value according to the current timestamp type. **/ public fun format(value: Long): String = "" + + public companion object { + /** + * Parse Discord's format specifiers to a specific format. + */ + public fun fromFormatSpecifier(string: String?): TimestampType? { + return when (string) { + "f" -> ShortDateTime + "F" -> LongDateTime + "d" -> ShortDate + "D" -> LongDate + "t" -> ShortTime + "T" -> LongTime + "R" -> RelativeTime + null -> Default + else -> null + } + } + } } diff --git a/kord-extensions/src/main/resources/translations/kordex/strings.properties b/kord-extensions/src/main/resources/translations/kordex/strings.properties index d714898225..5b8c1e3a3c 100644 --- a/kord-extensions/src/main/resources/translations/kordex/strings.properties +++ b/kord-extensions/src/main/resources/translations/kordex/strings.properties @@ -5,7 +5,6 @@ argumentParser.error.notAllValid=Argument `{0}` was provided with {1} {1, plural argumentParser.error.unknownConverterType=Unknown converter type provided\: `{0}` argumentParser.error.noFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}. argumentParser.error.someFilledArguments=This command has {0} required {0, plural, \=1 {argument} other {arguments}}, but only {1} could be filled. - channelType.dm=DM channelType.groupDm=Group DM channelType.guildCategory=Category @@ -18,9 +17,7 @@ channelType.publicNewsThread=News Thread channelType.publicGuildThread=Public Thread channelType.privateThread=Private Thread channelType.unknown=Unknown - checks.responseTemplate=**Error:** {0} - checks.inChannel.failed=Must be in **{0}** checks.notInChannel.failed=Must not be in **{0}** checks.inCategory.failed=Must be in category: **{0}** @@ -29,24 +26,18 @@ checks.channelHigher.failed=Must be in a channel higher than **{0}** checks.channelLower.failed=Must be in a channel lower than **{0}** checks.channelHigherOrEqual.failed=Must be in **{0}**, or a higher channel checks.channelLowerOrEqual.failed=Must be in **{0}**, or a lower channel - checks.anyGuild.failed=Must be in a server checks.noGuild.failed=Must not be in a server checks.inGuild.failed=Must be in server: **{0}** checks.notInGuild.failed=Must not be in server: **{0}** - checks.channelType.failed=Must be in a channel of type: **{0}** checks.notChannelType.failed=Must not be in a channel of type: **{0}** - checks.hasPermission.failed=Must have permission: **{0}** checks.notHasPermission.failed=Must not have permission: **{0}** - checks.isBot.failed=Must be a bot checks.isNotBot.failed=Must not be a bot - checks.isInThread.failed=Must be in a thread checks.isNotInThread.failed=Must not be in a thread - checks.hasRole.failed=Must have role: **{0}** checks.notHasRole.failed=Must not have role: **{0}** checks.topRoleEqual.failed=Must have top role: **{0}** @@ -55,7 +46,6 @@ checks.topRoleHigher.failed=Must have a top role higher than: **{0}** checks.topRoleLower.failed=Must have a top role lower than: **{0}** checks.topRoleHigherOrEqual.failed=Must have a top role of **{0}**, or a higher top role checks.topRoleLowerOrEqual.failed=Must have a top role of **{0}**, or a lower top role - commands.defaultDescription=No description provided. commands.error.missingBotPermissions=I don't have the permissions I need to run that command\!\n\n**Missing permissions\:** {0} commands.error.user=Unfortunately, **an error occurred** during command processing. Please let a staff member know. @@ -110,6 +100,8 @@ converters.union.error.unknownConverterType=Unknown converter type provided\: `{ converters.user.signatureType=user converters.user.error.missing=Unable to find user\: `{0}` converters.user.error.invalid=Value `{0}` is not a valid user ID. +converters.timestamp.signatureType=timestamp +converters.timestamp.error.invalid=Value `{0}` is not a valid timestamp. extensions.help.commandName=help extensions.help.commandAliases=h extensions.help.commandDescription=Get command help.\n\nSpecify the name of a command to get help for that specific command. Subcommands may also be specified, using the same form you'd use to run them. @@ -143,7 +135,6 @@ paginator.button.group.switch=Next Group paginator.button.less=Less paginator.footer.page=Page {0}/{1} paginator.footer.group=Group {0}/{1} - permission.addReactions=Add Reactions permission.administrator=Administrator permission.all=All Permissions @@ -182,7 +173,6 @@ permission.useVAD=Use Voice Activity permission.viewAuditLog=View Audit Log permission.viewChannel=View Channel permission.viewGuildInsights=View Server Insights - utils.message.useThisChannel=Please use {0} for this command. utils.message.commandNotAvailableInDm=This command is not available via private message. utils.colors.black=black,blck,blk diff --git a/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverterTest.kt b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverterTest.kt new file mode 100644 index 0000000000..2a6249e720 --- /dev/null +++ b/kord-extensions/src/test/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverterTest.kt @@ -0,0 +1,39 @@ +package com.kotlindiscord.kord.extensions.commands.converters.impl + +import com.kotlindiscord.kord.extensions.time.TimestampType +import kotlinx.datetime.Instant +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +internal class TimestampConverterTest { + + @Test + fun `timestamp without format`() { + val timestamp = "" // 1st second of 2015 + val parsed = TimestampConverter.parseFromString(timestamp)!! + assertEquals(Instant.fromEpochSeconds(1_420_070_400), parsed.instant) + assertEquals(TimestampType.Default, parsed.format) + } + + @Test + fun `timestamp with format`() { + val timestamp = "" + val parsed = TimestampConverter.parseFromString(timestamp)!! + assertEquals(Instant.fromEpochSeconds(1_420_070_400), parsed.instant) + assertEquals(TimestampType.RelativeTime, parsed.format) + } + + @Test + fun `empty timestamp`() { + val timestamp = "" + val parsed = TimestampConverter.parseFromString(timestamp) + assertNull(parsed) + } + + @Test + fun `timestamp with empty format`() { + val timestamp = "" + val parsed = TimestampConverter.parseFromString(timestamp) + assertNull(parsed) + } +} From 8cf7b57ec6e70d251b136c5eec56b7ec596a4de1 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Tue, 12 Oct 2021 12:26:46 +0100 Subject: [PATCH 131/131] KordEx 1.5.1-RC1 --- changes/1.5.1-RC1.md | 18 ++++++++++++++++++ crowdin.yml | 6 ------ gradle.properties | 2 +- .../builders/ExtensibleBotBuilder.kt | 6 +++--- 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 changes/1.5.1-RC1.md delete mode 100644 crowdin.yml diff --git a/changes/1.5.1-RC1.md b/changes/1.5.1-RC1.md new file mode 100644 index 0000000000..2b96176add --- /dev/null +++ b/changes/1.5.1-RC1.md @@ -0,0 +1,18 @@ +# KordEx 1.5.1-RC1 + +This release is our first "stable" release in quite a long time, targeting Kord `0.8.0-M7`. The reason for this is largely due to Kord's extremely long snapshot cycle, which itself was caused by many changes to Discord's APIs. In turn, this means that this release contains a mind-boggling number of internal changes. + +We've done our best to keep things as compatible as possible, API-wise. Despite this, though, we've had no choice but to break a few things. + +Highlights of this release: + +* With a lot of help from ByteAlex, we've been able to eliminate many, many unnecessary cache hits, making use of behaviors rather than entities wherever possible. This makes KordEx far more suitable for large bots with different caching requirements. +* Full support for message and user commands have been added, which come with a full rewrite of the application commands system. Application commands now always require a `public` or `ephemeral` type, to help keep things safe. Additionally, our old message commands are now named chat commands, and have their functions prefixed with `chat`. +* The components system has been fully rewritten, including a similar typing requirement to application commands. It comes with a `ComponentContainer` type which makes it easier to re-use components, as well as a callback registry for advanced use-cases (such as components that need to work after a restart). +* The Sentry integration has been rewritten, and you'll find a `SentryContext` provided everywhere you'd expect to be able to make use of Sentry, instead of a plain list of breadcrumbs. This, along with several other improvements, should make Sentry much more pleasant to work with. +* Our translations platform [has been switched to Weblate](https://hosted.weblate.org/engage/kord-extensions/). If you're a translator (or would like to help with translations), please head over there! +* Lots of deprecated things have now been removed. If you were still using them, well, you were warned! + +There are far too many changes to list here. The [existing pages on the wiki](https://kordex.kotlindiscord.com/) have been rewritten for this release, and we'd suggest taking a look at them to refresh your knowledge. There's still documentation work that needs doing, but we'll get there! + +As always, if you run into any problems, please let us know! diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index f0f1984840..0000000000 --- a/crowdin.yml +++ /dev/null @@ -1,6 +0,0 @@ -files: - - source: /kord-extensions/src/main/resources/translations/kordex/strings.properties - translation: >- - /kord-extensions/src/main/resources/translations/kordex/%file_name%_%locale_with_underscore%.%file_extension% - -"commit_message": "[ci skip]" diff --git a/gradle.properties b/gradle.properties index 900a4cc2f2..ebb2d0ed50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.incremental = true ksp.incremental = false -projectVersion = 1.5.1-SNAPSHOT +projectVersion = 1.5.1-RC1 #dokka will run out of memory with the default meta space org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m diff --git a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt index 6faa527f80..e528b581ec 100644 --- a/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/com/kotlindiscord/kord/extensions/builders/ExtensibleBotBuilder.kt @@ -851,7 +851,7 @@ public open class ExtensibleBotBuilder { * Requires the `GUILD_MEMBERS` privileged intent. Make sure you've enabled it for your bot! */ @JvmName("fillLongs") // These are the same for the JVM - public fun fill(ids: Collection): Boolean? = + public fun fill(ids: Collection): Boolean? = guildsToFill?.addAll(ids.map { Snowflake(it) }) /** @@ -876,7 +876,7 @@ public open class ExtensibleBotBuilder { * * Requires the `GUILD_MEMBERS` privileged intent. Make sure you've enabled it for your bot! */ - public fun fill(id: Long): Boolean? = + public fun fill(id: ULong): Boolean? = guildsToFill?.add(Snowflake(id)) /** @@ -1016,7 +1016,7 @@ public open class ExtensibleBotBuilder { } /** Set a guild ID to use for all global application commands. Intended for testing. **/ - public fun defaultGuild(id: Long) { + public fun defaultGuild(id: ULong) { defaultGuild = Snowflake(id) }