package cc.fascinated.bat.service; import cc.fascinated.bat.Consts; import cc.fascinated.bat.command.*; import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.config.Config; import cc.fascinated.bat.features.FeatureProfile; 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.*; /** * @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) { String commandName = command.getCommandInfo().name().toLowerCase(); if (commands.get(commandName) != null) { return; } log.info("Registered command \"{}\"", command.getCommandInfo().name()); commands.put(commandName, command); } /** * Registers all slash commands */ public void registerSlashCommands() { log.info("Registering all slash commands"); JDA jda = DiscordService.JDA; long before = System.currentTimeMillis(); Guild adminGuild = jda.getGuildById(Consts.ADMIN_GUILD); if (!Config.isProduction()) { if (adminGuild == null) { log.error("Unable to find the admin guild to register commands"); return; } jda.retrieveCommands().complete().forEach(command -> jda.deleteCommandById(command.getId()).complete()); List registeredCommands = adminGuild.updateCommands().addCommands(commands.values().stream().map(BatCommand::getCommandData).toList()).complete(); log.info("Registered {} slash commands in {}ms (DEV MODE)", registeredCommands.size(), System.currentTimeMillis() - before); return; } // Unregister all commands that Discord has but we don't jda.retrieveCommands().complete().forEach(command -> { if (commands.containsKey(command.getName()) && (commands.get(command.getName()).getCommandInfo().category().isHidden() || commands.get(command.getName()).getCommandInfo().botOwnerOnly())) { jda.deleteCommandById(command.getId()).complete(); // Unregister the command on Discord log.info("Unregistered hidden command \"{}\" from Discord", command.getName()); return; } 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() .filter(command -> !command.getCategory().isHidden() || !command.isBotOwnerOnly()) .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()); } } } if (adminGuild != null) { adminGuild.updateCommands().addCommands(commands.values().stream() .filter(command -> command.getCategory().isHidden() || command.isBotOwnerOnly()) .map(BatCommand::getCommandData).toList()).complete(); } else { log.error("Unable to find the admin guild to register hidden commands"); } log.info("Registered {} slash commands in {}ms", discordCommands.size(), System.currentTimeMillis() - before); } /** * Gets commands that are in a specific category * * @param category The category * @return The commands */ public List getCommandsByCategory(Category category, boolean hideHiddenCategories) { return commands.values().stream().filter(command -> command.getCategory() == category && (hideHiddenCategories && !category.isHidden())).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()); if (command.getCommandInfo().botOwnerOnly() && !user.getId().equalsIgnoreCase(Consts.BOT_OWNER)) { event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("You do not have permission to execute this command") .build()).setEphemeral(true).queue(); return; } try { BatCommandExecutor executor = null; CommandInfo commandInfo = command.getCommandInfo(); List requiredPermissions = new ArrayList<>(); boolean isSubCommand = false; // No args provided, use the main command executor if (event.getInteraction().getSubcommandName() == null) { executor = command; requiredPermissions.addAll(Arrays.asList(command.getCommandInfo().requiredPermissions())); } 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(Arrays.asList(subCommand.getValue().getCommandInfo().requiredPermissions())); requiredPermissions.addAll(Arrays.asList(command.getCommandInfo().requiredPermissions())); // not sure if we'd want this, but it's here for now isSubCommand = true; commandInfo = subCommand.getValue().getCommandInfo(); 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()) { StringBuilder missing = new StringBuilder(); for (Permission permission : missingPermissions) { missing.append("`").append(permission.getName()).append("`").append(", "); } event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("You are missing the following permissions to execute this command:\n" + missing.substring(0, missing.length() - 2)) .build()) .setEphemeral(true) .queue(); return; } } if (isSubCommand && commandInfo.guildOnly() && !ranInsideGuild) { event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("This command can only be executed in a guild") .build()).setEphemeral(true).queue(); return; } if (guild != null) { FeatureProfile featureProfile = guild.getFeatureProfile(); if (featureProfile.isFeatureDisabled(command.getFeature())) { event.replyEmbeds(EmbedUtils.errorEmbed() .setDescription("This command has been disabled by the guild owner") .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.errorEmbed() .setDescription(ex.getLocalizedMessage()) .build()) .setEphemeral(true) .queue(); } } }