diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordAutoCompleteHandler.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordAutoCompleteHandler.kt index 4ac3bb30..f0df40b7 100644 --- a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordAutoCompleteHandler.kt +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordAutoCompleteHandler.kt @@ -1,6 +1,76 @@ package io.github.shaksternano.borgar.discord.command +import dev.minn.jda.ktx.coroutines.await +import io.github.shaksternano.borgar.core.logger +import io.github.shaksternano.borgar.discord.DiscordManager +import io.github.shaksternano.borgar.messaging.command.COMMANDS import io.github.shaksternano.borgar.messaging.command.CommandAutoCompleteHandler +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent + +fun registerAutoCompleteHandlers() { + COMMANDS.values + .flatMap { command -> + command.argumentInfo.associateBy { command.name to it.key }.entries + } + .mapNotNull { (key, argumentInfo) -> + argumentInfo.autoCompleteHandler?.let { handler -> + Triple(key.first, key.second, handler) + } + } + .forEach { (command, option, handler) -> + registerAutoCompleteHandler(command, option, handler) + } +} + +suspend fun handleCommandAutoComplete(event: CommandAutoCompleteInteractionEvent) { + val command = event.name + val argument = event.focusedOption.name + val handler = getAutoCompleteHandler(command, argument) ?: return + val currentValue = event.focusedOption.value + val manager = DiscordManager[event.jda] + when (handler) { + is CommandAutoCompleteHandler.Long -> { + val longValue = currentValue.toLongOrNull() + if (longValue == null) { + logger.error("Invalid long value: $currentValue") + return + } + val values = handler.handleAutoComplete( + command, + argument, + longValue, + manager, + ) + event.replyChoiceLongs(values).await() + } + + is CommandAutoCompleteHandler.Double -> { + val doubleValue = currentValue.toDoubleOrNull() + if (doubleValue == null) { + logger.error("Invalid double value: $currentValue") + return + } + val values = handler.handleAutoComplete( + command, + argument, + doubleValue, + manager, + ) + event.replyChoiceDoubles(values).await() + } + + is CommandAutoCompleteHandler.String -> { + val values = handler.handleAutoComplete( + command, + argument, + currentValue, + manager, + ) + event.replyChoiceStrings(values).await() + } + } +} + private data class AutoCompleteHandlerKey( val command: String, @@ -9,10 +79,10 @@ private data class AutoCompleteHandlerKey( private val autoCompleteHandlers: MutableMap> = mutableMapOf() -fun registerAutoCompleteHandler(command: String, argument: String, handler: CommandAutoCompleteHandler<*>) { +private fun registerAutoCompleteHandler(command: String, argument: String, handler: CommandAutoCompleteHandler<*>) { autoCompleteHandlers[AutoCompleteHandlerKey(command, argument)] = handler } -fun getAutoCompleteHandler(command: String, argument: String): CommandAutoCompleteHandler<*>? { +private fun getAutoCompleteHandler(command: String, argument: String): CommandAutoCompleteHandler<*>? { return autoCompleteHandlers[AutoCompleteHandlerKey(command, argument)] } diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordCommands.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordCommands.kt index 30745d5b..d018d6e5 100644 --- a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordCommands.kt +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordCommands.kt @@ -4,19 +4,13 @@ import dev.minn.jda.ktx.coroutines.await import dev.minn.jda.ktx.events.listener import dev.minn.jda.ktx.interactions.commands.Command import dev.minn.jda.ktx.interactions.commands.updateCommands -import io.github.shaksternano.borgar.core.data.repository.TemplateRepository import io.github.shaksternano.borgar.core.logger import io.github.shaksternano.borgar.core.util.* -import io.github.shaksternano.borgar.discord.DiscordManager -import io.github.shaksternano.borgar.discord.entity.DiscordUser -import io.github.shaksternano.borgar.discord.entity.channel.DiscordMessageChannel import io.github.shaksternano.borgar.discord.event.DiscordMessageInteractionEvent import io.github.shaksternano.borgar.discord.event.DiscordUserInteractionEvent -import io.github.shaksternano.borgar.discord.event.SlashCommandEvent import io.github.shaksternano.borgar.discord.util.toDiscord import io.github.shaksternano.borgar.messaging.command.* import io.github.shaksternano.borgar.messaging.entity.* -import io.github.shaksternano.borgar.messaging.event.CommandEvent import io.github.shaksternano.borgar.messaging.event.MessageInteractionEvent import io.github.shaksternano.borgar.messaging.event.UserInteractionEvent import io.github.shaksternano.borgar.messaging.interaction.message.MESSAGE_INTERACTION_COMMANDS @@ -25,9 +19,8 @@ import io.github.shaksternano.borgar.messaging.interaction.message.handleMessage import io.github.shaksternano.borgar.messaging.interaction.user.USER_INTERACTION_COMMANDS import io.github.shaksternano.borgar.messaging.interaction.user.UserInteractionCommand import io.github.shaksternano.borgar.messaging.interaction.user.handleUserInteraction -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch import net.dv8tion.jda.api.JDA +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent @@ -51,12 +44,24 @@ suspend fun JDA.registerCommands() { listener { handleCommandAutoComplete(it) } + val commandModalInteractionName = "Run command" listener { - handleMessageInteraction(it.convert()) + if (it.name == commandModalInteractionName) { + createCommandModal(it) + } else { + handleMessageInteraction(it.convert()) + } } listener { handleUserInteraction(it.convert()) } + listener { + if (it.modalId == "command") { + handleModalCommand(it) + } else { + logger.error("Unknown modal ID: ${it.modalId}") + } + } registerAutoCompleteHandlers() updateCommands { val slashCommands = COMMANDS.values @@ -68,6 +73,10 @@ suspend fun JDA.registerCommands() { val userInteractionCommands = USER_INTERACTION_COMMANDS.values .map(UserInteractionCommand::toDiscord) addCommands(userInteractionCommands) + val commandModalInteraction = Commands.message(commandModalInteractionName) + .setContexts(InteractionContextType.ALL) + .setIntegrationTypes(IntegrationType.ALL) + addCommands(commandModalInteraction) }.await() } @@ -87,21 +96,6 @@ fun Command.toSlash(): SlashCommandData = Command(name, description) { ) } -fun registerAutoCompleteHandlers() { - COMMANDS.values - .flatMap { command -> - command.argumentInfo.associateBy { command.name to it.key }.entries - } - .mapNotNull { (key, argumentInfo) -> - argumentInfo.autoCompleteHandler?.let { handler -> - Triple(key.first, key.second, handler) - } - } - .forEach { (command, option, handler) -> - registerAutoCompleteHandler(command, option, handler) - } -} - private fun MessageInteractionCommand.toDiscord(): CommandData = Commands.message(name) .setContexts(environment.toDiscord()) @@ -128,140 +122,6 @@ private fun MessageContextInteractionEvent.convert(): MessageInteractionEvent = private fun UserContextInteractionEvent.convert(): UserInteractionEvent = DiscordUserInteractionEvent(this) -private suspend fun handleCommand(event: SlashCommandInteractionEvent) { - val commandName = event.name - val command = COMMANDS[commandName] ?: run { - val entityId = event.guild?.id ?: event.user.id - TemplateRepository.read(commandName, entityId)?.let(::TemplateCommand) - } - if (command == null) { - logger.error("Unknown command: $commandName") - event.reply("Unknown command!") - .setEphemeral(true) - .await() - return - } - val arguments = OptionCommandArguments(event, command.defaultArgumentKey) - val commandEvent = SlashCommandEvent(event) - executeCommand(command, arguments, commandEvent, event) -} - -private suspend fun handleCommandAutoComplete(event: CommandAutoCompleteInteractionEvent) { - val command = event.name - val argument = event.focusedOption.name - val handler = getAutoCompleteHandler(command, argument) ?: return - val currentValue = event.focusedOption.value - val manager = DiscordManager[event.jda] - when (handler) { - is CommandAutoCompleteHandler.Long -> { - val longValue = currentValue.toLongOrNull() - if (longValue == null) { - logger.error("Invalid long value: $currentValue") - return - } - val values = handler.handleAutoComplete( - command, - argument, - longValue, - manager, - ) - event.replyChoiceLongs(values).await() - } - - is CommandAutoCompleteHandler.Double -> { - val doubleValue = currentValue.toDoubleOrNull() - if (doubleValue == null) { - logger.error("Invalid double value: $currentValue") - return - } - val values = handler.handleAutoComplete( - command, - argument, - doubleValue, - manager, - ) - event.replyChoiceDoubles(values).await() - } - - is CommandAutoCompleteHandler.String -> { - val values = handler.handleAutoComplete( - command, - argument, - currentValue, - manager, - ) - event.replyChoiceStrings(values).await() - } - } -} - -private suspend fun executeCommand( - command: Command, - arguments: CommandArguments, - commandEvent: CommandEvent, - slashEvent: SlashCommandInteractionEvent, -) { - if (command.guildOnly && slashEvent.guild == null) { - logger.error("Guild only slash command $command used outside of a guild") - slashEvent.reply("${command.nameWithPrefix} can only be used in a server!") - .setEphemeral(true) - .await() - return - } - val afterCommands = arguments.getStringOrEmpty(AFTER_COMMANDS_ARGUMENT).let { - if (it.isBlank()) it - else if (!it.startsWith(COMMAND_PREFIX)) "$COMMAND_PREFIX$it" - else it - } - val slashCommandConfig = CommandConfig(command, arguments).asSingletonList() - val commandConfigs = if (afterCommands.isNotBlank()) { - try { - slashCommandConfig + getAfterCommandConfigs(afterCommands, commandEvent, slashEvent) - } catch (e: CommandNotFoundException) { - slashEvent.reply("The command **$COMMAND_PREFIX${e.command}** does not exist!") - .setEphemeral(true) - .await() - return - } - } else { - slashCommandConfig - } - val channel = commandEvent.getChannel() - val environment = channel.environment - commandEvent.ephemeralReply = commandConfigs.any { it.command.ephemeralReply } - val (responses, executable) = coroutineScope { - val anyDefer = commandConfigs.any { it.command.deferReply } - if (anyDefer) launch { - commandEvent.deferReply() - } - executeCommands(commandConfigs, environment, commandEvent) - } - sendResponses(responses, executable, commandEvent, channel) -} - -private suspend fun getAfterCommandConfigs( - afterCommands: String, - commandEvent: CommandEvent, - slashEvent: SlashCommandInteractionEvent, -): List { - val configs = parseCommands( - afterCommands, - FakeMessage( - commandEvent.id, - afterCommands, - DiscordUser(slashEvent.user), - DiscordMessageChannel(slashEvent.channel), - ), - ) - if (configs.isEmpty()) { - val firstCommand = afterCommands.splitWords(limit = 2) - .first() - .substring(1) - throw CommandNotFoundException(firstCommand) - } - return configs -} - private fun CommandArgumentInfo<*>.toOption(): OptionData { val description = description + (defaultValue?.let { " Default value: ${it.formatted}" diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordModalCommandHandler.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordModalCommandHandler.kt new file mode 100644 index 00000000..8e12ec8c --- /dev/null +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordModalCommandHandler.kt @@ -0,0 +1,121 @@ +package io.github.shaksternano.borgar.discord.command + +import dev.minn.jda.ktx.coroutines.await +import dev.minn.jda.ktx.interactions.components.Modal +import dev.minn.jda.ktx.interactions.components.TextInput +import io.github.shaksternano.borgar.core.logger +import io.github.shaksternano.borgar.discord.entity.DiscordMessage +import io.github.shaksternano.borgar.discord.entity.DiscordUser +import io.github.shaksternano.borgar.discord.entity.channel.DiscordMessageChannel +import io.github.shaksternano.borgar.discord.event.DiscordInteractionCommandEvent +import io.github.shaksternano.borgar.messaging.MessagingPlatform +import io.github.shaksternano.borgar.messaging.command.COMMAND_PREFIX +import io.github.shaksternano.borgar.messaging.command.CommandNotFoundException +import io.github.shaksternano.borgar.messaging.command.isCorrectEnvironment +import io.github.shaksternano.borgar.messaging.command.parseCommands +import io.github.shaksternano.borgar.messaging.entity.FakeMessage +import io.github.shaksternano.borgar.messaging.event.CommandEvent +import io.github.shaksternano.borgar.messaging.executeAndRespond +import io.github.shaksternano.borgar.messaging.util.getAndExpireSelectedMessage +import io.github.shaksternano.borgar.messaging.util.setSelectedMessage +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent +import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent +import net.dv8tion.jda.api.interactions.components.ActionRow +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle + +suspend fun createCommandModal(event: MessageContextInteractionEvent) { + val command = TextInput( + id = "command", + label = "Command", + style = TextInputStyle.PARAGRAPH, + ) { + placeholder = "Enter the command you want to execute on this message" + builder.minLength = 1 + } + val modal = Modal( + id = "command", + title = "Run command", + ) { + components += ActionRow.of(command) + } + val channelId = event.channelId + if (channelId != null) { + setSelectedMessage( + userId = event.user.id, + channelId = channelId, + platform = MessagingPlatform.DISCORD, + message = DiscordMessage(event.target), + ) + } + event.replyModal(modal).await() +} + +suspend fun handleModalCommand(event: ModalInteractionEvent) { + val content = event.getValue("command") + ?.asString + ?.trim() + ?.let { + if (it.startsWith(COMMAND_PREFIX)) { + it + } else { + "$COMMAND_PREFIX$it" + } + } + ?: run { + logger.error("Command value is null") + return + } + runCatching { + val channel = DiscordMessageChannel(event.channel) + val message = FakeMessage( + id = event.id, + content = content, + author = DiscordUser(event.user), + channel = channel, + ) + val commandConfigs = try { + parseCommands(content, message) + } catch (e: CommandNotFoundException) { + event.reply("The command **$COMMAND_PREFIX${e.command}** does not exist!") + .setEphemeral(true) + .await() + return + } + if (commandConfigs.isEmpty()) { + event.reply("No commands found!") + .setEphemeral(true) + .await() + return + } + val environment = channel.environment + val firstCommand = commandConfigs.first().command + if (!firstCommand.isCorrectEnvironment(environment)) { + event.reply("The command **$COMMAND_PREFIX${firstCommand.name}** cannot be used in a ${environment.displayName.lowercase()} channel!") + .setEphemeral(true) + .await() + return + } + val commandEvent = createModalCommandEvent(event) + commandEvent.executeAndRespond(commandConfigs) + }.getOrElse { + logger.error("Error handling modal command $content", it) + event.reply("An error occurred!") + .setEphemeral(true) + .await() + } +} + +private fun createModalCommandEvent(event: ModalInteractionEvent): CommandEvent { + val discordChannel = event.channel + // Message interaction event message is not preserved so modal event message is null + val referencedMessage = getAndExpireSelectedMessage( + userId = event.user.id, + channelId = discordChannel.id, + platform = MessagingPlatform.DISCORD, + ) + return DiscordInteractionCommandEvent( + event, + discordChannel, + referencedMessage = referencedMessage, + ) +} diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/OptionCommandArguments.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordOptionCommandArguments.kt similarity index 98% rename from discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/OptionCommandArguments.kt rename to discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordOptionCommandArguments.kt index 3ebcd13a..c42db35f 100644 --- a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/OptionCommandArguments.kt +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordOptionCommandArguments.kt @@ -16,7 +16,7 @@ import io.github.shaksternano.borgar.messaging.entity.Attachment import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload import net.dv8tion.jda.api.interactions.commands.OptionType -class OptionCommandArguments( +class DiscordOptionCommandArguments( private val interaction: CommandInteractionPayload, override val defaultKey: String?, ) : CommandArguments { @@ -101,7 +101,7 @@ class OptionCommandArguments( if (this === other) return true if (kClass != other?.kClass) return false - other as OptionCommandArguments + other as DiscordOptionCommandArguments if (interaction != other.interaction) return false if (defaultKey != other.defaultKey) return false diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordSlashCommandHandler.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordSlashCommandHandler.kt new file mode 100644 index 00000000..baff1980 --- /dev/null +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/command/DiscordSlashCommandHandler.kt @@ -0,0 +1,114 @@ +package io.github.shaksternano.borgar.discord.command + +import dev.minn.jda.ktx.coroutines.await +import io.github.shaksternano.borgar.core.data.repository.TemplateRepository +import io.github.shaksternano.borgar.core.logger +import io.github.shaksternano.borgar.core.util.asSingletonList +import io.github.shaksternano.borgar.core.util.splitWords +import io.github.shaksternano.borgar.discord.DiscordManager +import io.github.shaksternano.borgar.discord.entity.DiscordUser +import io.github.shaksternano.borgar.discord.entity.channel.DiscordMessageChannel +import io.github.shaksternano.borgar.discord.event.DiscordInteractionCommandEvent +import io.github.shaksternano.borgar.messaging.command.* +import io.github.shaksternano.borgar.messaging.entity.Attachment +import io.github.shaksternano.borgar.messaging.entity.FakeMessage +import io.github.shaksternano.borgar.messaging.event.CommandEvent +import io.github.shaksternano.borgar.messaging.executeAndRespond +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent +import net.dv8tion.jda.api.interactions.commands.OptionType + +suspend fun handleCommand(event: SlashCommandInteractionEvent) { + val commandName = event.name + val command = COMMANDS[commandName] ?: run { + val entityId = event.guild?.id ?: event.user.id + TemplateRepository.read(commandName, entityId)?.let(::TemplateCommand) + } + if (command == null) { + logger.error("Unknown command: $commandName") + event.reply("Unknown command!") + .setEphemeral(true) + .await() + return + } + val arguments = DiscordOptionCommandArguments(event, command.defaultArgumentKey) + val commandEvent = createSlashCommandEvent(event) + executeSlashCommand(command, arguments, commandEvent, event) +} + +private fun createSlashCommandEvent(event: SlashCommandInteractionEvent): CommandEvent { + val manager = DiscordManager[event.jda] + val discordChannel = event.channel + val attachments = event.getOptionsByType(OptionType.ATTACHMENT).map { + val attachment = it.asAttachment + Attachment( + id = attachment.id, + url = attachment.url, + proxyUrl = attachment.proxyUrl, + filename = attachment.fileName, + manager = manager, + ephemeral = true, + ) + } + return DiscordInteractionCommandEvent( + event, + discordChannel, + attachments, + ) +} + +private suspend fun executeSlashCommand( + command: Command, + arguments: CommandArguments, + commandEvent: CommandEvent, + slashEvent: SlashCommandInteractionEvent, +) { + if (command.guildOnly && slashEvent.guild == null) { + logger.error("Guild only slash command $command used outside of a guild") + slashEvent.reply("${command.nameWithPrefix} can only be used in a server!") + .setEphemeral(true) + .await() + return + } + val afterCommands = arguments.getStringOrEmpty(AFTER_COMMANDS_ARGUMENT).let { + if (it.isBlank()) it + else if (!it.startsWith(COMMAND_PREFIX)) "$COMMAND_PREFIX$it" + else it + } + val slashCommandConfig = CommandConfig(command, arguments).asSingletonList() + val commandConfigs = if (afterCommands.isNotBlank()) { + try { + slashCommandConfig + getAfterCommandConfigs(afterCommands, commandEvent, slashEvent) + } catch (e: CommandNotFoundException) { + slashEvent.reply("The command **$COMMAND_PREFIX${e.command}** does not exist!") + .setEphemeral(true) + .await() + return + } + } else { + slashCommandConfig + } + commandEvent.executeAndRespond(commandConfigs) +} + +private suspend fun getAfterCommandConfigs( + afterCommands: String, + commandEvent: CommandEvent, + slashEvent: SlashCommandInteractionEvent, +): List { + val configs = parseCommands( + afterCommands, + FakeMessage( + commandEvent.id, + afterCommands, + DiscordUser(slashEvent.user), + DiscordMessageChannel(slashEvent.channel), + ), + ) + if (configs.isEmpty()) { + val firstCommand = afterCommands.splitWords(limit = 2) + .first() + .substring(1) + throw CommandNotFoundException(firstCommand) + } + return configs +} diff --git a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/SlashCommandEvent.kt b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/DiscordInteractionCommandEvent.kt similarity index 76% rename from discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/SlashCommandEvent.kt rename to discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/DiscordInteractionCommandEvent.kt index 8e00461f..73c5956e 100644 --- a/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/SlashCommandEvent.kt +++ b/discord/src/main/kotlin/io/github/shaksternano/borgar/discord/event/DiscordInteractionCommandEvent.kt @@ -19,32 +19,35 @@ import io.github.shaksternano.borgar.messaging.entity.channel.MessageChannel import io.github.shaksternano.borgar.messaging.event.CommandEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf import net.dv8tion.jda.api.entities.channel.concrete.GroupChannel -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent +import net.dv8tion.jda.api.interactions.Interaction import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback -import net.dv8tion.jda.api.interactions.commands.OptionType import net.dv8tion.jda.api.utils.messages.MessageCreateData import java.time.OffsetDateTime -class SlashCommandEvent( - private val discordEvent: SlashCommandInteractionEvent -) : CommandEvent { +class DiscordInteractionCommandEvent( + private val discordEvent: T, + discordChannel: net.dv8tion.jda.api.entities.channel.middleman.MessageChannel, + private val attachments: List = emptyList(), + referencedMessage: Message? = null, +) : CommandEvent where T : Interaction, T : IReplyCallback { override val manager: BotManager = DiscordManager[discordEvent.jda] override val id: String = discordEvent.id override val authorId: String = discordEvent.user.id override val timeCreated: OffsetDateTime = discordEvent.timeCreated - override val referencedMessages: Flow = emptyFlow() + override val referencedMessages: Flow = + referencedMessage?.let { flowOf(it) } ?: emptyFlow() private val user: User = DiscordUser(discordEvent.user) private val member: Member? = discordEvent.member?.let { DiscordMember(it) } private val channel: MessageChannel = DiscordMessageChannel( - discordEvent.channel, + discordChannel, discordEvent.context, ) private val guild: Guild? = discordEvent.guild?.let { DiscordGuild(it) } private val group: Group? = run { - val discordChannel = discordEvent.channel if (discordChannel is GroupChannel) { DiscordGroup(discordChannel) } else { @@ -97,37 +100,27 @@ class SlashCommandEvent( override fun asMessageIntersection(arguments: CommandArguments): CommandMessageIntersection = object : CommandMessageIntersection { - override val id: String = this@SlashCommandEvent.id - override val authorId: String = this@SlashCommandEvent.authorId - override val manager: BotManager = this@SlashCommandEvent.manager + override val id: String = this@DiscordInteractionCommandEvent.id + override val authorId: String = this@DiscordInteractionCommandEvent.authorId + override val manager: BotManager = this@DiscordInteractionCommandEvent.manager override val content: String = arguments.getDefaultStringOrEmpty() - override val attachments: List = discordEvent.getOptionsByType(OptionType.ATTACHMENT).map { - val attachment = it.asAttachment - Attachment( - id = attachment.id, - url = attachment.url, - proxyUrl = attachment.proxyUrl, - filename = attachment.fileName, - manager = manager, - ephemeral = true, - ) - } + override val attachments: List = this@DiscordInteractionCommandEvent.attachments override val customEmojis: Flow = manager.getCustomEmojis(content) override val stickers: Flow = emptyFlow() - override val referencedMessages: Flow = emptyFlow() + override val referencedMessages: Flow = this@DiscordInteractionCommandEvent.referencedMessages override val mentionedUsers: Flow = emptyFlow() override val mentionedChannels: Flow = emptyFlow() override val mentionedRoles: Flow = emptyFlow() - override suspend fun getAuthor(): User = this@SlashCommandEvent.getAuthor() + override suspend fun getAuthor(): User = this@DiscordInteractionCommandEvent.getAuthor() - override suspend fun getAuthorMember(): Member? = this@SlashCommandEvent.getAuthorMember() + override suspend fun getAuthorMember(): Member? = this@DiscordInteractionCommandEvent.getAuthorMember() - override suspend fun getChannel(): MessageChannel = this@SlashCommandEvent.getChannel() + override suspend fun getChannel(): MessageChannel = this@DiscordInteractionCommandEvent.getChannel() - override suspend fun getGuild(): Guild? = this@SlashCommandEvent.getGuild() + override suspend fun getGuild(): Guild? = this@DiscordInteractionCommandEvent.getGuild() - override suspend fun getGroup(): Group? = this@SlashCommandEvent.getGroup() + override suspend fun getGroup(): Group? = this@DiscordInteractionCommandEvent.getGroup() override suspend fun getEmbeds(): List = emptyList() } diff --git a/messaging/src/main/kotlin/io/github/shaksternano/borgar/messaging/Messaging.kt b/messaging/src/main/kotlin/io/github/shaksternano/borgar/messaging/Messaging.kt index ccb71b6e..70ee3da4 100644 --- a/messaging/src/main/kotlin/io/github/shaksternano/borgar/messaging/Messaging.kt +++ b/messaging/src/main/kotlin/io/github/shaksternano/borgar/messaging/Messaging.kt @@ -1,7 +1,27 @@ package io.github.shaksternano.borgar.messaging +import io.github.shaksternano.borgar.messaging.command.CommandConfig import io.github.shaksternano.borgar.messaging.command.DerpibooruCommand +import io.github.shaksternano.borgar.messaging.command.executeCommands +import io.github.shaksternano.borgar.messaging.command.sendResponses +import io.github.shaksternano.borgar.messaging.event.CommandEvent +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch suspend fun initMessaging() { DerpibooruCommand.loadTags() } + +suspend fun CommandEvent.executeAndRespond(commandConfigs: List) { + val channel = getChannel() + val environment = channel.environment + ephemeralReply = commandConfigs.any { it.command.ephemeralReply } + val (responses, executable) = coroutineScope { + val anyDefer = commandConfigs.any { it.command.deferReply } + if (anyDefer) launch { + deferReply() + } + executeCommands(commandConfigs, environment, this@executeAndRespond) + } + sendResponses(responses, executable, this, channel) +}