From 4f666d77515835e48eb7ecdd3ada702c1991492e Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 10 May 2023 21:12:24 -0400 Subject: [PATCH 1/4] Make slash commands work --- .../kotlin/de/bigboot/ggtools/fang/Command.kt | 76 ++++++++-- .../de/bigboot/ggtools/fang/CommandContext.kt | 10 +- .../de/bigboot/ggtools/fang/commands/Root.kt | 42 +++--- .../ggtools/fang/commands/admin/Server.kt | 24 ++- .../fang/commands/admin/group/Group.kt | 24 ++- .../fang/commands/admin/group/Permissions.kt | 22 ++- .../fang/commands/admin/group/Users.kt | 41 +++--- .../ggtools/fang/commands/queue/Queue.kt | 32 ++-- .../ggtools/fang/commands/server/Server.kt | 70 +++++---- .../ggtools/fang/service/CommandsService.kt | 137 ++++++++++++++---- .../ggtools/fang/utils/Discord4JExt.kt | 2 +- 11 files changed, 303 insertions(+), 177 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt index de3aa25..c0f6b48 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt @@ -1,5 +1,13 @@ package de.bigboot.ggtools.fang +import de.bigboot.ggtools.fang.service.AutostartService +import discord4j.discordjson.json.ApplicationCommandOptionData; +import discord4j.discordjson.json.ApplicationCommandRequest; +import discord4j.discordjson.json.ImmutableApplicationCommandOptionData; +import discord4j.core.GatewayDiscordClient +import discord4j.core.`object`.command.ApplicationCommandOption; +import discord4j.core.spec.* +import org.koin.core.component.inject import org.koin.core.component.KoinComponent data class Argument( @@ -17,7 +25,7 @@ abstract class CommandGroupSpec(val name: String, val description: String) : Koi abstract val build: CommandGroupBuilder.() -> Unit } -sealed class Command(val name: String, val description: String) { +sealed class Command(val name: String, val description: String, var slashCommand: ImmutableApplicationCommandOptionData.Builder) { var parent: Command? = null internal set @@ -25,12 +33,30 @@ sealed class Command(val name: String, val description: String) { name: String, description: String, val args: Array, - val handler: suspend CommandContext.() -> Unit + val handler: suspend CommandContext.() -> MessageCreateSpec.Builder.() -> Unit ) : - Command(name, description) + Command(name, description, run { + var command = ApplicationCommandOptionData.builder() + .name(name) + .description(description) + .type(ApplicationCommandOption.Type.SUB_COMMAND.getValue()); + args.forEach { + command = command.addOption(ApplicationCommandOptionData.builder() + .name(it.name) + .description(it.description) + .required(!it.optional) + .type(ApplicationCommandOption.Type.STRING.getValue()) + .build()); + } + command + }) + class Group(name: String, description: String, override val commands: Map) : - Command(name, description), Commands { + Command(name, description, ApplicationCommandOptionData.builder() + .name(name) + .description(description) + .type(ApplicationCommandOption.Type.SUB_COMMAND_GROUP.getValue())), Commands { operator fun plus(other: Command): Group { return Group( @@ -62,14 +88,14 @@ sealed class Command(val name: String, val description: String) { } class CommandBuilder(private val name: String, private val description: String) { - private var handler: suspend CommandContext.() -> Unit = {} + private var handler: suspend CommandContext.() -> MessageCreateSpec.Builder.() -> Unit = {{}} private var args = ArrayList() fun arg(name: String, description: String = "", optional: Boolean = false, verify: ((String) -> Boolean)? = null) { this.args.add(Argument(name, description, optional, verify)) } - fun onCall(handler: suspend CommandContext.() -> Unit) { + fun onCall(handler: suspend CommandContext.() -> MessageCreateSpec.Builder.() -> Unit) { this.handler = handler } @@ -88,13 +114,27 @@ class CommandBuilder(private val name: String, private val description: String) } .toTypedArray() ) + + fun args(): Array = + this@CommandBuilder.args + .sortedBy { + if (it.optional) { + 1 + } else { + 0 + } + } + .toTypedArray() } -class CommandGroupBuilder(private val name: String, private val description: String) { +class CommandGroupBuilder(private val name: String, private val description: String) : AutostartService, KoinComponent { + private val client by inject() private val commands = HashMap() fun command(name: String, description: String = "", builder: CommandBuilder.() -> Unit = {}) { - commands[name] = CommandBuilder(name, description).apply(builder).build() + var commandBuilt = CommandBuilder(name, description).apply(builder); + + commands[name] = commandBuilt.build() } fun group(spec: CommandGroupSpec) { @@ -108,5 +148,23 @@ class CommandGroupBuilder(private val name: String, private val description: Str name = name, description = description, commands = commands - ).apply { commands.values.forEach { it.parent = this } } + ).apply { + commands.values.forEach { it.parent = this }; + + var command = ApplicationCommandRequest.builder() + .name(name) + .description(description) + .type(ApplicationCommandOption.Type.SUB_COMMAND.getValue()); + commands.values.forEach { + command = command.addOption(it.slashCommand.build()); + } + + val applicationId = client.getRestClient().getApplicationId().block(); + + commands.values.forEach { + client.getRestClient().getApplicationService() + .createGlobalApplicationCommand(applicationId, command.build()) + .subscribe(); + } + } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/CommandContext.kt b/src/main/kotlin/de/bigboot/ggtools/fang/CommandContext.kt index 1854b03..c84d891 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/CommandContext.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/CommandContext.kt @@ -2,6 +2,7 @@ package de.bigboot.ggtools.fang import discord4j.core.`object`.entity.Guild import discord4j.core.`object`.entity.Message +import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.KoinComponent @@ -9,13 +10,16 @@ import org.koin.core.component.inject data class CommandContext( val args: Arguments, - val message: Message + val channel: MessageChannel, + val guild: Guild, + var author: User ) : KoinComponent { private val _commands: Commands by inject() val commands = _commands.commands - suspend fun channel(): MessageChannel = message.channel.awaitSingle() - suspend fun guild(): Guild = message.guild.awaitSingle() + suspend fun channel(): MessageChannel = channel + suspend fun guild(): Guild = guild + suspend fun author(): User = author class Arguments(private val arguments: Map) { operator fun get(key: String) = arguments.getValue(key) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt index 6a11311..45862a8 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/Root.kt @@ -5,9 +5,7 @@ import de.bigboot.ggtools.fang.CommandGroupSpec import de.bigboot.ggtools.fang.commands.admin.Admin import de.bigboot.ggtools.fang.commands.queue.Queue import de.bigboot.ggtools.fang.commands.server.Server -import de.bigboot.ggtools.fang.utils.createEmbedCompat -import de.bigboot.ggtools.fang.utils.formatCommandHelp -import de.bigboot.ggtools.fang.utils.formatCommandTree +import de.bigboot.ggtools.fang.utils.* import kotlinx.coroutines.reactive.awaitFirst class Root : CommandGroupSpec("", "") { @@ -18,28 +16,32 @@ class Root : CommandGroupSpec("", "") { command("help", "show this help") { onCall { - channel().createEmbedCompat { - title("Help") - addField( - "commands", - commands.values.joinToString("\n\n") { - "${formatCommandHelp( - it.name, - it - )}\n${it.description}" - }, - false - ) - }.awaitFirst() + { + addEmbedCompat { + title("Help") + addField( + "commands", + commands.values.joinToString("\n\n") { + "${formatCommandHelp( + it.name, + it + )}\n${it.description}" + }, + false + ) + } + } } } command("commands", "Show all available commands") { onCall { - channel().createEmbedCompat { - title("Commands") - description("```\n${formatCommandTree(commands.values)}\n```") - }.awaitFirst() + { + addEmbedCompat { + title("Commands") + description("```\n${formatCommandTree(commands.values)}\n```") + } + } } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/Server.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/Server.kt index 290c395..6bdc88f 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/Server.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/Server.kt @@ -3,9 +3,7 @@ package de.bigboot.ggtools.fang.commands.admin import de.bigboot.ggtools.fang.CommandGroupBuilder import de.bigboot.ggtools.fang.CommandGroupSpec import de.bigboot.ggtools.fang.service.ServerService -import de.bigboot.ggtools.fang.utils.addEmbedCompat -import de.bigboot.ggtools.fang.utils.createEmbedCompat -import de.bigboot.ggtools.fang.utils.editCompat +import de.bigboot.ggtools.fang.utils.* import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.inject @@ -23,23 +21,19 @@ class Server : CommandGroupSpec("server", "Commands for managing servers") { val url = args["url"] val apiKey = args["api_key"] - val msg = channel().createEmbedCompat { - description("Adding $name to the list of servers") - }.awaitSingle() - if (serverService.checkServer(name, url, apiKey)) { serverService.addServer(name, url, apiKey) - msg.editCompat { + return@onCall { addEmbedCompat { description("Adding $name to the list of servers -> Success") } - }.awaitSingle() + } } else { - msg.editCompat { + return@onCall { addEmbedCompat { description("Adding $name to the list of servers -> Failed") } - }.awaitSingle() + } } } } @@ -52,13 +46,13 @@ class Server : CommandGroupSpec("server", "Commands for managing servers") { if (serverService.getClient(name) != null) { serverService.removeServer(name) - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("$name removed from the list of servers.") - }.awaitSingle() + }} } else { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("$name not the list of servers.") - }.awaitSingle() + }} } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Group.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Group.kt index b4dcb58..66da3e3 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Group.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Group.kt @@ -3,7 +3,7 @@ package de.bigboot.ggtools.fang.commands.admin.group import de.bigboot.ggtools.fang.CommandGroupBuilder import de.bigboot.ggtools.fang.Config import de.bigboot.ggtools.fang.service.PermissionService -import de.bigboot.ggtools.fang.utils.createEmbedCompat +import de.bigboot.ggtools.fang.utils.* import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.inject @@ -16,11 +16,11 @@ class Group : de.bigboot.ggtools.fang.CommandGroupSpec("group", "Commands for ma command("list", "List all groups") { onCall { - channel().createEmbedCompat { + {addEmbedCompat{ title("Groups") description(permissionService.getGroups() .joinToString("\n")) - }.awaitSingle() + }} } } @@ -29,12 +29,12 @@ class Group : de.bigboot.ggtools.fang.CommandGroupSpec("group", "Commands for ma onCall { val group = args["group"] - channel().createEmbedCompat { + {addEmbedCompat{ title("Permissions for group $group") description( permissionService.getPermissions(group) ?.joinToString("\n") ?: "Group not found") - }.awaitSingle() + }} } } @@ -44,12 +44,12 @@ class Group : de.bigboot.ggtools.fang.CommandGroupSpec("group", "Commands for ma onCall { val group = args["group"] - channel().createEmbedCompat { + {addEmbedCompat{ description(when { permissionService.addGroup(group) -> "The group $group has been created" else -> "The group $group already exists" }) - }.awaitSingle() + }} } } @@ -60,19 +60,17 @@ class Group : de.bigboot.ggtools.fang.CommandGroupSpec("group", "Commands for ma val group = args["group"] if (group == Config.permissions.default_group_name || group == Config.permissions.admin_group_name) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("The default and admin groups cannot be deleted") - }.awaitSingle() - - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat{ description(when { permissionService.removeGroup(group) -> "The group $group has been deleted" else -> "The group $group was not found" }) - }.awaitSingle() + }} } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Permissions.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Permissions.kt index 8e6d52a..846132e 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Permissions.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Permissions.kt @@ -4,7 +4,7 @@ import de.bigboot.ggtools.fang.CommandGroupBuilder import de.bigboot.ggtools.fang.CommandGroupSpec import de.bigboot.ggtools.fang.Config import de.bigboot.ggtools.fang.service.PermissionService -import de.bigboot.ggtools.fang.utils.createEmbedCompat +import de.bigboot.ggtools.fang.utils.* import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.inject @@ -21,14 +21,12 @@ class Permissions : CommandGroupSpec("permissions", "Manage group permissions") val permission = args["permission"] if (group == Config.permissions.admin_group_name) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("The admin groups cannot be modified") - }.awaitSingle() - - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat{ description( when { permissionService.addPermissionToGroup( @@ -38,7 +36,7 @@ class Permissions : CommandGroupSpec("permissions", "Manage group permissions") else -> "Group $group not found" } ) - }.awaitSingle() + }} } } @@ -51,14 +49,12 @@ class Permissions : CommandGroupSpec("permissions", "Manage group permissions") val permission = args["permission"] if (group == Config.permissions.admin_group_name) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("The admin groups cannot be modified") - }.awaitSingle() - - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat{ description( when { permissionService.removePermissionFromGroup( @@ -68,7 +64,7 @@ class Permissions : CommandGroupSpec("permissions", "Manage group permissions") else -> "Group $group not found" } ) - }.awaitSingle() + }} } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Users.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Users.kt index 90c2934..521d87a 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Users.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/admin/group/Users.kt @@ -3,9 +3,7 @@ package de.bigboot.ggtools.fang.commands.admin.group import de.bigboot.ggtools.fang.CommandGroupBuilder import de.bigboot.ggtools.fang.CommandGroupSpec import de.bigboot.ggtools.fang.service.PermissionService -import de.bigboot.ggtools.fang.utils.createEmbedCompat -import de.bigboot.ggtools.fang.utils.findMember -import de.bigboot.ggtools.fang.utils.findUser +import de.bigboot.ggtools.fang.utils.* import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.inject @@ -23,15 +21,14 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { val user = guild().findUser(userName) if (user == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("User $userName not found") - }.awaitSingle() - return@onCall + }} } val groups = permissionService.getGroupsByUser(user.id.asLong()) - channel().createEmbedCompat { + return@onCall {addEmbedCompat { title("Permissions of ${member?.displayName ?: user.username}") groups.forEach { group -> addField(group.first, group.second.joinToString("\n").ifBlank { "-" }, false) @@ -39,8 +36,7 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { if (groups.isEmpty()) { description("User <@${user.id.asString()}> doesn't have any permissions") } - }.awaitSingle() - return@onCall + }} } } @@ -55,13 +51,12 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { val user = guild().findUser(userName) if (user == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("User $userName not found") - }.awaitSingle() - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat { description( when { permissionService.addUserToGroup( @@ -71,7 +66,7 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { else -> "Group $group not found" } ) - }.awaitSingle() + }} } } @@ -86,20 +81,19 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { val user = guild().findUser(userName) if (user == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("User $userName not found") - }.awaitSingle() - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat { description( when { permissionService.removeUserFromGroup(user.id.asLong(), group) -> "<@${user.id.asString()}> has been removed from the group $group" else -> "Group $group not found" } ) - }.awaitSingle() + }} } } @@ -111,16 +105,15 @@ class Users : CommandGroupSpec("users", "Commands for managing users") { val users = permissionService.getUsersByGroup(group) if (users == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Group $group not found") - }.awaitSingle() - return@onCall + }} } - channel().createEmbedCompat { + {addEmbedCompat { title("Users in group: $group") description(users.joinToString("\n") { "<@${it.snowflake}>" }) - }.awaitSingle() + }} } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/queue/Queue.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/queue/Queue.kt index 54a5957..f095bad 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/queue/Queue.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/queue/Queue.kt @@ -17,12 +17,12 @@ class Queue : CommandGroupSpec("queue", "Commands for matchmaking") { override val build: CommandGroupBuilder.() -> Unit = { command("list", "show all available queues") { onCall { - channel().createMessageCompat { + { addEmbedCompat { title("Available queues:") description(Config.bot.queues.joinToString("\n") { it.name }) } - }.awaitSingle() + } } } @@ -32,11 +32,11 @@ class Queue : CommandGroupSpec("queue", "Commands for matchmaking") { onCall { val queue = args["queue"] - channel().createMessageCompat { + { addEmbedCompat { queueMessageService.printQueue(queue, this) } - }.awaitSingle() + } } } @@ -49,17 +49,16 @@ class Queue : CommandGroupSpec("queue", "Commands for matchmaking") { val queue = args["queue"] if (user == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("User ${args["player"]} not found") - }.awaitSingle() - return@onCall + }} } matchService.leave(queue, user.id.asLong()) - channel().createEmbedCompat { + return@onCall {addEmbedCompat{ description("<@${user.id.asString()}> removed from the queue.") - }.awaitSingle() + }} } } @@ -72,20 +71,21 @@ class Queue : CommandGroupSpec("queue", "Commands for matchmaking") { val queue = args["queue"] if (players == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Sorry, I didn't understand that.") - }.awaitSingle() - return@onCall + }} } if (players <= 0) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("You need at least 1 player to request a match!") - }.awaitSingle() - return@onCall + }} } - matchService.request(queue, Snowflake.of(message.userData.id()).asLong(), players) + matchService.request(queue, Snowflake.of(author().userData.id()).asLong(), players) + return@onCall {addEmbedCompat { + description("I have created the request!") + }} } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/commands/server/Server.kt b/src/main/kotlin/de/bigboot/ggtools/fang/commands/server/Server.kt index abe310f..5257882 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/commands/server/Server.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/commands/server/Server.kt @@ -6,9 +6,7 @@ import de.bigboot.ggtools.fang.api.agent.model.AdminPWRequest import de.bigboot.ggtools.fang.api.agent.model.KillRequest import de.bigboot.ggtools.fang.api.agent.model.StartRequest import de.bigboot.ggtools.fang.service.ServerService -import de.bigboot.ggtools.fang.utils.createEmbedCompat -import de.bigboot.ggtools.fang.utils.createMessageCompat -import de.bigboot.ggtools.fang.utils.orNull +import de.bigboot.ggtools.fang.utils.* import discord4j.core.`object`.reaction.ReactionEmoji import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactive.awaitSingle @@ -21,11 +19,11 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { override val build: CommandGroupBuilder.() -> Unit = { command("list", "List all servers") { onCall { - channel().createEmbedCompat { + {addEmbedCompat { title("Servers") description(serverService.getAllServers() .joinToString { "${it.name} -> ${it.url}" }) - }.awaitSingle() + }} } } @@ -48,11 +46,9 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { val client = serverService.getClient(server) if (client == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Unknown server $server.") - }.awaitSingle() - - return@onCall + }} } val request = StartRequest( @@ -66,14 +62,18 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { val response = client.start(request) if (response.openUrl != null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("open ${response.openUrl}") - }.awaitSingle() + }} } else if (response.error != null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Couldn't start the server: ${response.error}.") - }.awaitSingle() + }} } + + return@onCall {addEmbedCompat { + description("A unknow error happened :pensive:") + }} } } @@ -88,11 +88,9 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { val client = serverService.getClient(server) if (client == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Unknown server $server.") - }.awaitSingle() - - return@onCall + }} } val response = client.kill( @@ -102,13 +100,13 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { ) if (response.error != null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Couldn't kill the server: ${response.error}.") - }.awaitSingle() + }} } else { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Server killed") - }.awaitSingle() + }} } } } @@ -124,11 +122,9 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { val client = serverService.getClient(server) if (client == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("Unknown server $server.") - }.awaitSingle() - - return@onCall + }} } val response = client.getAdminPW( @@ -138,15 +134,13 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { ) if (response.adminPW == null) { - channel().createEmbedCompat { + return@onCall {addEmbedCompat { description("No admin password available, are you sure the instance is running?") - }.awaitSingle() - - return@onCall + }} } - val privateMessage = message.author.orNull() - ?.privateChannel + val privateMessage = author() + .privateChannel ?.awaitSingle() ?.createMessageCompat { content("Admin password for this instance: ${response.adminPW}") @@ -154,12 +148,16 @@ class Server : CommandGroupSpec("server", "Commands for controlling servers") { ?.onErrorResume { Mono.empty() } ?.awaitFirstOrNull() - if (privateMessage != null) { - message.addReaction(ReactionEmoji.unicode("\uD83D\uDC4C")).awaitFirstOrNull() - } else { - channel().createEmbedCompat { + if (privateMessage == null) { + return@onCall {addEmbedCompat { description("Could not send a DM.\nMake sure you can receive direct messages from server members.") - }.awaitSingle() + }} + } + else { + return@onCall {addEmbedCompat { + description("Sent you the admin password!") + }} + } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/service/CommandsService.kt b/src/main/kotlin/de/bigboot/ggtools/fang/service/CommandsService.kt index ca2fec7..5d3ef36 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/service/CommandsService.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/service/CommandsService.kt @@ -5,8 +5,11 @@ import de.bigboot.ggtools.fang.commands.Root import de.bigboot.ggtools.fang.utils.* import discord4j.common.util.Snowflake import discord4j.core.GatewayDiscordClient +import discord4j.core.`object`.command.ApplicationCommandOption; import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent import discord4j.core.event.domain.message.MessageCreateEvent +import discord4j.core.spec.* import kotlinx.coroutines.flow.filter import kotlinx.coroutines.reactive.awaitSingle import org.koin.core.component.KoinComponent @@ -24,6 +27,10 @@ class CommandsService : AutostartService, KoinComponent { .filter { it.message.content.startsWith(Config.bot.prefix) } .onEachSafe(this::handleCommandEvent) .launch() + + client.eventDispatcher.on() + .onEachSafe(this::handleSlashEvent) + .launch() } private suspend fun handleCommandEvent(event: MessageCreateEvent) { @@ -73,6 +80,8 @@ class CommandsService : AutostartService, KoinComponent { return } + var send: (MessageCreateSpec.Builder.() -> Unit)? = null; + when (command) { is Command.Invokable -> { val argList = args.asSequence().toList() @@ -80,19 +89,23 @@ class CommandsService : AutostartService, KoinComponent { if (commandArgs != null) { val invalidArgs = verfiyCommandArguments(command, argList) if (invalidArgs.isEmpty()) { - command.handler(CommandContext(commandArgs, msg)) + send = command.handler(CommandContext(commandArgs, msg.channel.awaitSingle(), msg.guild.awaitSingle(), msg.authorAsMember.awaitSingle())) } else { printInvalidArguments(msg.channel.awaitSingle(), command, invalidArgs) } } else { - printCommandHelp(msg.channel.awaitSingle(), command) + send = printCommandHelp(command) } } is Command.Group -> { - printCommandHelp(msg.channel.awaitSingle(), command) + send = printCommandHelp(command) } } + + send.let{ + msg.channel.awaitSingle().createMessage(MessageCreateSpec.builder().apply(it!!).build()).awaitSingle(); + } } private fun createCommandArguments(command: Command.Invokable, args: Collection): CommandContext.Arguments? { @@ -122,32 +135,102 @@ class CommandsService : AutostartService, KoinComponent { }.awaitSingle() } - private suspend fun printCommandHelp(channel: MessageChannel, command: Command) { - channel.createEmbedCompat { - title("Usage: ${formatCommandHelp(command.fullname, command)}") - description(command.description) - - when (command) { - is Command.Invokable -> { - addField( - "arguments", - command.args.joinToString("\n\n") { "*${it.name}*\n${it.description}" }, - false - ) + private suspend fun printCommandHelp(command: Command): MessageCreateSpec.Builder.() -> Unit { + return { + addEmbedCompat { + title("Usage: ${formatCommandHelp(command.fullname, command)}") + description(command.description) + + when (command) { + is Command.Invokable -> { + addField( + "arguments", + command.args.joinToString("\n\n") { "*${it.name}*\n${it.description}" }, + false + ) + } + is Command.Group -> { + addField( + "subcommands", + command.commands.values.joinToString("\n\n") { + "${formatCommandHelp( + it.name, + it + )}\n${it.description}" + }, + false + ) + } } - is Command.Group -> { - addField( - "subcommands", - command.commands.values.joinToString("\n\n") { - "${formatCommandHelp( - it.name, - it - )}\n${it.description}" - }, - false - ) + } + } + } + + private suspend fun handleSlashEvent(event: ChatInputInteractionEvent) { + event.deferReply().awaitSafe() + + val interaction = event.interaction; + + var args = event.getOptions(); + + var command: Command = commands.commands[event.getCommandName()] ?: return; + while (args.size > 0 && command is Command.Group) { + val next = args.first(); + args = next.getOptions(); + command = command.commands[next.name] ?: return; + } + + val namespace = command.namespace + val hasPermission = permissionService + .getGroupsByUser(interaction.user.id.asLong()) + .flatMap { it.second } + .toSet() + .any { permission -> + permission + .replace(".", "\\.") + .replace("*", ".*") + .toRegex() + .matches(namespace) + } + + if (!hasPermission) { + event.editReplyCompat { + addEmbedCompat { + description("You do not have permissions to use this command") } + }.await() + return + } + + var returnCommand: MessageCreateSpec.Builder.() -> Unit?; + + when (command) { + + is Command.Invokable -> { + val args = args.map { it.value.get().asString() }.toList(); + val commandArgs = createCommandArguments(command, args)!!; + returnCommand = command.handler(CommandContext(commandArgs, interaction.channel.awaitSingle(), interaction.guild.awaitSingle(), interaction.user)) } - }.awaitSingle() + + is Command.Group -> { + returnCommand = printCommandHelp(command); + } + } + + returnCommand.let{ + val command = MessageCreateSpec.builder().apply(it).build() + + var interaction = InteractionReplyEditSpec.builder(); + + if (!command.content().isAbsent()) { + command.content().get().let{interaction = interaction.content(it)} + } + + if (!command.embeds().isAbsent()) { + command.embeds().get().let{it.forEach { interaction = interaction.addEmbed(it)}} + } + + event.editReply(interaction.build()).awaitSingle() + } } } diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/utils/Discord4JExt.kt b/src/main/kotlin/de/bigboot/ggtools/fang/utils/Discord4JExt.kt index aa3b840..07ea664 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/utils/Discord4JExt.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/utils/Discord4JExt.kt @@ -169,4 +169,4 @@ fun InteractionReplyEditSpec.Builder.addEmbedCompat(spec: EmbedCreateSpec.Builde @Suppress("HasPlatformType") fun DeferrableInteractionEvent.editReplyCompat(spec: InteractionReplyEditSpec.Builder.() -> Unit) = - editReply(InteractionReplyEditSpec.builder().apply(spec).build()) \ No newline at end of file + editReply(InteractionReplyEditSpec.builder().apply(spec).build()) From aa98ae85ec262f0295dc17436c606f6d92ec5e7f Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Wed, 10 May 2023 21:17:52 -0400 Subject: [PATCH 2/4] remove redundent code --- src/main/kotlin/de/bigboot/ggtools/fang/Command.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt index c0f6b48..74d088b 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt @@ -132,9 +132,7 @@ class CommandGroupBuilder(private val name: String, private val description: Str private val commands = HashMap() fun command(name: String, description: String = "", builder: CommandBuilder.() -> Unit = {}) { - var commandBuilt = CommandBuilder(name, description).apply(builder); - - commands[name] = commandBuilt.build() + commands[name] = CommandBuilder(name, description).apply(builder); } fun group(spec: CommandGroupSpec) { From 8a0490fe534c851eb031312e3e35128a786def65 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Thu, 11 May 2023 18:28:17 -0400 Subject: [PATCH 3/4] fix my making the code smaller --- src/main/kotlin/de/bigboot/ggtools/fang/Command.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt index 74d088b..60fba1c 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt @@ -132,7 +132,7 @@ class CommandGroupBuilder(private val name: String, private val description: Str private val commands = HashMap() fun command(name: String, description: String = "", builder: CommandBuilder.() -> Unit = {}) { - commands[name] = CommandBuilder(name, description).apply(builder); + commands[name] = CommandBuilder(name, description).apply(builder).build(); } fun group(spec: CommandGroupSpec) { From 83f8b913f1b9c2ef15433675ad9b1ea6b325ec62 Mon Sep 17 00:00:00 2001 From: Arthur Melton Date: Thu, 11 May 2023 18:41:01 -0400 Subject: [PATCH 4/4] remove code runing multipul times for no reason --- src/main/kotlin/de/bigboot/ggtools/fang/Command.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt index 60fba1c..f911bea 100644 --- a/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt +++ b/src/main/kotlin/de/bigboot/ggtools/fang/Command.kt @@ -159,10 +159,8 @@ class CommandGroupBuilder(private val name: String, private val description: Str val applicationId = client.getRestClient().getApplicationId().block(); - commands.values.forEach { - client.getRestClient().getApplicationService() - .createGlobalApplicationCommand(applicationId, command.build()) - .subscribe(); - } + client.getRestClient().getApplicationService() + .createGlobalApplicationCommand(applicationId, command.build()) + .subscribe(); } }