package cc.fascinated.bat.service; import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommandExecutor; import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.Category; import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatUser; import lombok.Getter; import lombok.NonNull; import lombok.extern.log4j.Log4j2; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.commands.Command; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Fascinated (fascinated7) */ @Service @Log4j2 @Getter @DependsOn("discordService") public class CommandService extends ListenerAdapter { /** * The registered commands */ private final Map commands = new HashMap<>(); /** * The guild service to use */ private final GuildService guildService; /** * The user service to use */ private final UserService userService; @Autowired public CommandService(@NonNull GuildService guildService, @NonNull UserService userService) { this.guildService = guildService; this.userService = userService; DiscordService.JDA.addEventListener(this); } /** * Registers a command * * @param command The command to register */ public void registerCommand(@NonNull BatCommand command) { if (commands.get(command.getName().toLowerCase()) != null) { return; } log.info("Registered command \"{}\"", command.getName()); commands.put(command.getName().toLowerCase(), command); } /** * Registers all slash commands */ public void registerSlashCommands() { log.info("Registering all slash commands"); JDA jda = DiscordService.JDA; long before = System.currentTimeMillis(); // Unregister all commands that Discord has but we don't jda.retrieveCommands().complete().forEach(command -> { if (commands.containsKey(command.getName())) { return; } jda.deleteCommandById(command.getId()).complete(); // Unregister the command on Discord log.info("Unregistered unknown command \"{}\" from Discord", command.getName()); }); // Register all commands List discordCommands = jda.updateCommands().addCommands(commands.values().stream().map(BatCommand::getCommandData).toList()).complete(); for (Command discordCommand : discordCommands) { commands.get(discordCommand.getName()).setCommandSnowflake(discordCommand.getIdLong()); if (!discordCommand.getSubcommands().isEmpty()) { for (Command.Subcommand subCommand : discordCommand.getSubcommands()) { commands.get(discordCommand.getName()).getSubCommands().get(subCommand.getName()).setCommandSnowflake(subCommand.getIdLong()); } } } log.info("Registered all slash commands in {}ms", System.currentTimeMillis() - before); } /** * Gets commands that are in a specific category * * @param category The category * @return The commands */ public List getCommandsByCategory(Category category) { return commands.values().stream().filter(command -> command.getCategory() == category).toList(); } @Override public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) { Guild discordGuild = event.getGuild(); if (event.getUser().isBot()) { return; } String commandName = event.getName(); BatCommand command = commands.get(commandName); if (command == null) { return; } boolean ranInsideGuild = discordGuild != null; BatGuild guild = ranInsideGuild ? guildService.getGuild(discordGuild.getId()) : null; BatUser user = userService.getUser(event.getUser().getId()); try { BatCommandExecutor executor = null; List requiredPermissions = new ArrayList<>(); // No args provided, use the main command executor if (event.getInteraction().getSubcommandName() == null) { executor = command; requiredPermissions.addAll(command.getRequiredPermissions()); } else { // Subcommand provided, use the subcommand executor for (Map.Entry subCommand : command.getSubCommands().entrySet()) { if (subCommand.getKey().equalsIgnoreCase(event.getInteraction().getSubcommandName())) { executor = subCommand.getValue(); requiredPermissions.addAll(subCommand.getValue().getRequiredPermissions()); requiredPermissions.addAll(command.getRequiredPermissions()); // not sure if we'd want this, but it's here for now break; } } } if (executor == null) { event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("Unable to find a command executor the command name ):") .build()).queue(); return; } // Check if the user has the required permissions if (ranInsideGuild && event.getMember() != null) { List missingPermissions = new ArrayList<>(); for (Permission permission : requiredPermissions) { if (!event.getMember().hasPermission(permission)) { missingPermissions.add(permission); } } if (!missingPermissions.isEmpty()) { event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("You are missing the following permissions to execute this command: " + missingPermissions) .build()) .setEphemeral(true) .queue(); return; } } log.info("Executing command \"{}\" for user \"{}\"", commandName, user.getDiscordUser().getName()); executor.execute(guild, user, ranInsideGuild ? event.getChannel().asTextChannel() : event.getChannel().asPrivateChannel(), event.getMember(), event.getInteraction()); } catch (Exception ex) { log.error("An error occurred while executing command \"{}\"", commandName, ex); event.replyEmbeds(EmbedUtils.successEmbed() .setDescription("An error occurred while executing the command\n\n" + ex.getLocalizedMessage()) .build()) .queue(); } } }