From 2afab8f1f55bfc60eb85a056a3258d2476a62777 Mon Sep 17 00:00:00 2001 From: anthorob Date: Wed, 11 Dec 2024 04:00:55 -0500 Subject: [PATCH] Create abstraction to combine slash commands and message commands Usage and options checking (arguments count, type checking) Options parsing for message commands (both types of commands are now registered like slash commands and the abstraction will handle the parsing of all options as if it was a slash command) Refactor of the help command for better readability and more helpful text, which is now based on the available options of a command --- .../bobert/netneutrality/NetNeutrality.java | 3 +- .../command/AbstractCommand.java | 10 - .../bobert/netneutrality/command/Command.java | 15 +- .../netneutrality/command/CommandOptions.java | 129 +++++++++ .../command/admin/BlacklistCommand.java | 26 +- .../command/admin/ReactionCommand.java | 38 +-- .../command/basic/HelpCommand.java | 50 ++-- .../command/basic/PingCommand.java | 17 +- .../command/bot/ConfigureCommand.java | 156 +---------- .../command/crypto/CryptoPriceCommand.java | 66 +---- .../command/git/ReportIssueCommand.java | 8 +- .../command/music/BitconnectCommand.java | 19 +- .../command/music/ClearQueueCommand.java | 8 +- .../music/CurrentlyPlayingCommand.java | 26 +- .../command/music/JoinCommand.java | 18 +- .../command/music/LeaveCommand.java | 21 +- .../command/music/PlayCommand.java | 27 +- .../command/music/QueueCommand.java | 31 +-- .../command/music/ResumeCommand.java | 8 +- .../command/music/ShuffleCommand.java | 8 +- .../command/music/SkipCommand.java | 47 +--- .../command/music/StopCommand.java | 26 +- .../command/music/ViewQueueCommand.java | 25 +- .../command/music/VolumeCommand.java | 46 +--- .../command/music/VoteSkipCommand.java | 20 +- .../netneutrality/context/AsyncContext.java | 8 +- .../context/MessageCommandContext.java | 259 ++++++++++++++---- .../context/MusicChannelContext.java | 6 + .../context/SlashCommandContext.java | 31 ++- .../context/tasks/ButtonTaskContext.java | 6 + .../context/tasks/EmojiTaskContext.java | 10 + .../context/tasks/ModalTaskContext.java | 6 + .../core/command/CommandContainer.java | 40 +++ .../core/command/CommandManager.java | 52 ++-- .../core/command/CommandProcessor.java | 183 ++++++++++--- .../builder/CommandOptionsBuilder.java | 24 ++ .../core/command/options/CommandOption.java | 164 +++++++++++ .../command/options/MessageCommandOption.java | 149 ++++++++++ .../command/options/SlashCommandOption.java | 90 ++++++ .../core/context/CommandContext.java | 106 ++++++- .../netneutrality/core/context/Context.java | 27 +- .../netneutrality/core/tasks/BuiltInTask.java | 13 +- .../exceptions/CommandExecutionException.java | 4 - .../netneutrality/tasks/help/BotInfo.java | 2 +- .../netneutrality/tasks/help/CommandHelp.java | 4 +- .../netneutrality/tasks/help/ReportBug.java | 2 +- .../MusicPlayerAutoDisconnectThread.java | 2 +- .../netneutrality/utils/ContextUtils.java | 78 ++++++ .../netneutrality/utils/MessageFunction.java | 36 +-- .../netneutrality/utils/MusicUtils.java | 9 + .../netneutrality/utils/UserFunction.java | 12 +- 51 files changed, 1461 insertions(+), 710 deletions(-) create mode 100644 src/main/java/me/bobert/netneutrality/command/CommandOptions.java create mode 100644 src/main/java/me/bobert/netneutrality/core/command/CommandContainer.java create mode 100644 src/main/java/me/bobert/netneutrality/core/command/builder/CommandOptionsBuilder.java create mode 100644 src/main/java/me/bobert/netneutrality/core/command/options/CommandOption.java create mode 100644 src/main/java/me/bobert/netneutrality/core/command/options/MessageCommandOption.java create mode 100644 src/main/java/me/bobert/netneutrality/core/command/options/SlashCommandOption.java create mode 100644 src/main/java/me/bobert/netneutrality/utils/ContextUtils.java diff --git a/src/main/java/me/bobert/netneutrality/NetNeutrality.java b/src/main/java/me/bobert/netneutrality/NetNeutrality.java index ce0abab..3daf612 100644 --- a/src/main/java/me/bobert/netneutrality/NetNeutrality.java +++ b/src/main/java/me/bobert/netneutrality/NetNeutrality.java @@ -69,10 +69,11 @@ public class NetNeutrality { private void startup() { // DatabaseMigration.migrate(); SessionManager.getSessionFactory(); - initJDA(); CommandManager.init(jda); MusicManager.initPlayerManager(); TaskManager.getInstance().init(); + initJDA(); + CommandManager.registerSlashCommands(jda); jda.addEventListener(new ServerJoinHook()); jda.addEventListener(new MessageReceivedHook(), new SlashCommandHook()); jda.addEventListener(new ModalInteractionHook(), new ButtonInteractionHook(), new EmojiReactionHook()); diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java index 771a040..2b6582f 100644 --- a/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java @@ -6,9 +6,6 @@ package me.bobert.netneutrality.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.core.command.CommandExecutionResult; -import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -34,11 +31,4 @@ public abstract class AbstractCommand implements Command { public boolean canBeExecutedFrom(MessageChannel channel) { return true; } - - @Deprecated - @Override - public CommandExecutionResult execute(MessageCommandContext context) { - // Prefer slash commands - return CommandExecutionResult.none(); - } } diff --git a/src/main/java/me/bobert/netneutrality/command/Command.java b/src/main/java/me/bobert/netneutrality/command/Command.java index 193781d..8b34cac 100644 --- a/src/main/java/me/bobert/netneutrality/command/Command.java +++ b/src/main/java/me/bobert/netneutrality/command/Command.java @@ -5,14 +5,12 @@ package me.bobert.netneutrality.command; * https://discord.gg/qUa6zNFZsd */ -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; -import net.dv8tion.jda.api.entities.Guild; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public interface Command { @@ -28,9 +26,9 @@ public interface Command { String getDescription(); - DefaultMemberPermissions getSlashCommandPermission(); + void registerCommandOptions(CommandOptionsBuilder builder); - void registerSlashCommandOptions(SlashCommandData data); + DefaultMemberPermissions getSlashCommandPermission(); CommandType getType(); @@ -38,8 +36,5 @@ public interface Command { boolean canBeExecutedFrom(MessageChannel channel); - @Deprecated - CommandExecutionResult execute(MessageCommandContext context); - - CommandExecutionResult execute(SlashCommandContext context); + CommandExecutionResult execute(CommandContext context); } diff --git a/src/main/java/me/bobert/netneutrality/command/CommandOptions.java b/src/main/java/me/bobert/netneutrality/command/CommandOptions.java new file mode 100644 index 0000000..6eb9a59 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/command/CommandOptions.java @@ -0,0 +1,129 @@ +package me.bobert.netneutrality.command; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; +import net.dv8tion.jda.internal.interactions.CommandDataImpl; + +import java.util.List; + +public class CommandOptions { + private final Command command; + private final SlashCommandData commandData; + private final List options; + private final List subcommands; + private final List subcommandGroups; + + public CommandOptions(Command command, CommandDataImpl commandData) { + this.command = command; + this.commandData = commandData; + this.options = commandData.getOptions(); + this.subcommands = commandData.getSubcommands(); + this.subcommandGroups = commandData.getSubcommandGroups(); + } + + public Command getCommand() { + return command; + } + + public SlashCommandData getSlashCommandData() { + return commandData; + } + + public List getOptions() { + return options; + } + + public List getSubcommands() { + return subcommands; + } + + public List getSubcommandGroups() { + return subcommandGroups; + } + + public boolean isEmpty() { + return options.isEmpty() && subcommands.isEmpty() && subcommandGroups.isEmpty(); + } + + public String printUsage() { + //TODO: show available choices if option has them + StringBuilder usage = new StringBuilder(); + if (!options.isEmpty()) { + usage.append("- Usage: /").append(command.getCommandName()); + for (OptionData option : options) { + usage.append(" <").append(option.getName()).append(">"); + } + + for (OptionData option : options) { + usage.append("\n - "); + if (option.isRequired()) { + usage.append("*"); + } + usage.append(option.getName()).append(": ").append(option.getDescription()); + } + } + + if (!subcommands.isEmpty()) { + for (SubcommandData subcommand : subcommands) { + usage.append("- Usage: /").append(command.getCommandName()).append(" ").append(subcommand.getName()); + for (OptionData option : subcommand.getOptions()) { + usage.append(" <").append(option.getName()).append(">"); + } + + for (OptionData option : subcommand.getOptions()) { + usage.append("\n - "); + if (option.isRequired()) { + usage.append("*"); + } + usage.append(option.getName()).append(": ").append(option.getDescription()); + } + + usage.append("\n"); + } + } + + if (!subcommandGroups.isEmpty()) { + for (SubcommandGroupData subcommandGroup : subcommandGroups) { + usage.append("- Subcommand: /").append(command.getCommandName()) + .append(" ").append(subcommandGroup.getName()) + .append(" (Description: ").append(subcommandGroup.getDescription()).append(")") + .append("\n"); + + for (SubcommandData subcommand : subcommandGroup.getSubcommands()) { + usage.append(" - Usage: /").append(command.getCommandName()) + .append(" ").append(subcommandGroup.getName()) + .append(" ").append(subcommand.getName()); + + for (OptionData option : subcommand.getOptions()) { + usage.append(" <").append(option.getName()).append(">"); + } + usage.append(" (Description: ").append(subcommand.getDescription()).append(")"); + + for (OptionData option : subcommand.getOptions()) { + usage.append("\n - "); + if (option.isRequired()) { + usage.append("*"); + } + usage.append(option.getName()).append(": ").append(option.getDescription()); + } + + usage.append("\n"); + + } + } + } + + if (!isEmpty()) { + usage.append("\n\n-# *: required argument"); + } + + return usage.toString(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java index caf7e2b..3ac006d 100644 --- a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java @@ -6,14 +6,13 @@ package me.bobert.netneutrality.command.admin; */ import me.bobert.netneutrality.command.AbstractAdminCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.db.entities.OBlacklist; import me.bobert.netneutrality.db.repository.BlacklistRepository; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import java.util.List; @@ -41,25 +40,20 @@ public class BlacklistCommand extends AbstractAdminCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addSubcommands( + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addSubcommands( new SubcommandData("get", "Get all blacklisted words for this guild"), new SubcommandData("add", "Add a word to the blacklist") - .addOption(OptionType.STRING, "word", "Word to add"), + .addOption(OptionType.STRING, "word", "Word to add", true), new SubcommandData("remove", "Remove a word from the blacklist") - .addOption(OptionType.STRING, "word", "Word to remove") + .addOption(OptionType.STRING, "word", "Word to remove", true) ); } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - String sub = context.getEvent().getFullCommandName().split(" ")[1]; - String word = context.getEvent().getOption("word", "", OptionMapping::getAsString); + public CommandExecutionResult execute(CommandContext context) { + String sub = context.getSubcommand(); + String word = context.getOption("word", "", CommandOption::getAsString); BlacklistRepository repository = new BlacklistRepository(); switch (sub) { diff --git a/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java index f6d5ce1..9e7a19f 100644 --- a/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/admin/ReactionCommand.java @@ -6,14 +6,14 @@ package me.bobert.netneutrality.command.admin; */ import me.bobert.netneutrality.command.AbstractAdminCommand; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.tasks.TaskManager; import me.bobert.netneutrality.tasks.emoji.EmojiReactionRoleTask; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; @@ -40,8 +40,8 @@ public class ReactionCommand extends AbstractAdminCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addSubcommandGroups( + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addSubcommandGroups( new SubcommandGroupData("emoji", "Give roles to users using emojis").addSubcommands( new SubcommandData("setup", "Add a role to a message").addOptions( new OptionData(OptionType.STRING, "messageid", "The ID of the message", true), @@ -65,19 +65,19 @@ public class ReactionCommand extends AbstractAdminCommand { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { context.ack(); String guildId = context.getGuild().getId(); - String messageId = context.getEvent().getOption("messageid", OptionMapping::getAsString); - String sub = context.getSubCommand(); - String action = context.getEvent().getFullCommandName().split(" ")[2]; + String messageId = context.getOption("messageid", CommandOption::getAsString); + String group = context.getSubcommandGroup(); + String sub = context.getSubcommand(); - switch (sub) { + switch (group) { case "emoji" -> { - if ("setup".equals(action)) { - String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); - String role = context.getEvent().getOption("role", OptionMapping::getAsString); + if ("setup".equals(sub)) { + String emoji = context.getOption("emoji", CommandOption::getAsString); + String role = context.getOption("role", CommandOption::getAsString); EmojiReactionRoleTask task = new EmojiReactionRoleTask(guildId, messageId); if (TaskManager.getInstance().exists(task.getId())) { @@ -89,9 +89,9 @@ public class ReactionCommand extends AbstractAdminCommand { return CommandExecutionResult.success("Added emoji to message successfully!"); } - if ("add".equals(action)) { - String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); - String role = context.getEvent().getOption("role", OptionMapping::getAsString); + if ("add".equals(sub)) { + String emoji = context.getOption("emoji", CommandOption::getAsString); + String role = context.getOption("role", CommandOption::getAsString); String id = String.format("%s:%s", guildId, messageId); EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); @@ -104,8 +104,8 @@ public class ReactionCommand extends AbstractAdminCommand { return CommandExecutionResult.success("Added emoji to message successfully!"); } - if ("remove".equals(action)) { - String emoji = context.getEvent().getOption("emoji", OptionMapping::getAsString); + if ("remove".equals(sub)) { + String emoji = context.getOption("emoji", CommandOption::getAsString); String id = String.format("%s:%s", guildId, messageId); EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); @@ -118,7 +118,7 @@ public class ReactionCommand extends AbstractAdminCommand { return CommandExecutionResult.success("Removed emoji from message successfully!"); } - if ("delete".equals(action)) { + if ("delete".equals(sub)) { String id = String.format("%s:%s", guildId, messageId); EmojiReactionRoleTask task = (EmojiReactionRoleTask) TaskManager.getInstance().getTask(id); if (task == null) { diff --git a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java index 3345f59..3200882 100644 --- a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java @@ -6,24 +6,21 @@ package me.bobert.netneutrality.command.basic; */ import me.bobert.netneutrality.command.AbstractCommand; -import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.CommandContainer; import me.bobert.netneutrality.core.command.CommandExecutionResult; import me.bobert.netneutrality.core.command.CommandManager; -import me.bobert.netneutrality.core.context.Context; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.tasks.BuiltInTask; import me.bobert.netneutrality.tasks.help.BotInfo; import me.bobert.netneutrality.tasks.help.CommandHelp; import me.bobert.netneutrality.tasks.help.ReportBug; import me.bobert.netneutrality.utils.MessageFunction; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.components.ActionRow; import net.dv8tion.jda.api.interactions.components.buttons.Button; -import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import java.util.List; @@ -55,9 +52,8 @@ public class HelpCommand extends AbstractCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - - data.addOption(OptionType.STRING, "command", "The name of the command you want help with", false); + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addOption(OptionType.STRING, "command", "The name of the command you want help with", false); } @Override @@ -66,37 +62,25 @@ public class HelpCommand extends AbstractCommand { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - context.reply(new MessageCreateBuilder().addContent(context.getDiscordUser().getAsMention()).addContent(" help has been sent your way!").build()); - sendHelp(context, context.getArgument(0)); - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - sendHelp(context, context.getEvent().getOption("command", null, OptionMapping::getAsString)); - - return CommandExecutionResult.success("Help has been sent in DM!"); - } - - public void sendHelp(Context context, String commandStr) { - if (commandStr == null) { + public CommandExecutionResult execute(CommandContext context) { + String command = context.getOption("command", null, CommandOption::getAsString); + if (command == null) { Button commands = BuiltInTask.createButton(CommandHelp.class).primary("Commands"); Button info = BuiltInTask.createButton(BotInfo.class).primary("Info"); Button reportbug = BuiltInTask.createButton(ReportBug.class).danger("Report bug"); ActionRow row = ActionRow.of(commands, info, reportbug); + context.replyEphemeral("Help has been sent in DM!"); context.sendMessageInPrivate("Thanks for using the Net Neutrality Bot! What do you need help with?", List.of(row)); - return; + return CommandExecutionResult.none(); } - Command command = CommandManager.getCommand(commandStr); - if (command == null) { - context.reply("Command does not exist"); - return; + CommandContainer container = CommandManager.getCommandContainer(command); + if (container.getCommand() == null) { + return CommandExecutionResult.warn("Command does not exist"); } - context.reply(MessageFunction.buildCommandHelpMessage(command)); + context.replyEphemeral("Help has been sent in DM!"); + context.sendMessageInPrivate(MessageFunction.buildCommandHelpMessage(container)); + return CommandExecutionResult.none(); } - } diff --git a/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java index b84210d..73ff6ba 100644 --- a/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java @@ -7,10 +7,9 @@ package me.bobert.netneutrality.command.basic; import me.bobert.netneutrality.command.AbstractCommand; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; public class PingCommand extends AbstractCommand { @@ -40,7 +39,7 @@ public class PingCommand extends AbstractCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @@ -50,15 +49,7 @@ public class PingCommand extends AbstractCommand { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - if (context.getArgs().isEmpty()) - context.reply(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing())); - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { return CommandExecutionResult.success(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing())); } diff --git a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java index a1852b6..bb3c411 100644 --- a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java @@ -6,23 +6,21 @@ package me.bobert.netneutrality.command.bot; */ import me.bobert.netneutrality.command.AbstractAdminCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.GuildManager; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.db.GSettings; import me.bobert.netneutrality.music.MusicManager; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; -import java.util.List; import java.util.Map; public class ConfigureCommand extends AbstractAdminCommand { @@ -48,11 +46,11 @@ public class ConfigureCommand extends AbstractAdminCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addSubcommands( + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addSubcommands( new SubcommandData("music-volume", "Volume for the music player") .addOptions(new OptionData(OptionType.INTEGER, "volume", "Integer between 0 and 100", true) - .setRequiredRange(0, 100)), + .setRequiredRange(0, 100)), new SubcommandData("error-logging", "Send errors to #error and/or #log channel") .addOption(OptionType.BOOLEAN, "debug", "Enable or disable the fonctionnality", true), new SubcommandData("music-role", "Role needed to use music commands") @@ -63,143 +61,13 @@ public class ConfigureCommand extends AbstractAdminCommand { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - Map args = context.getArgs(); - GuildManager guildManager = GuildManager.getManager(context.getGuild()); - - if (!args.isEmpty()) { - - // Role for music commands - if (context.getArgument(0).equals("music-role")) { - if (args.size() == 1) { - String roleDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE); - Role role = null; - if (!roleDb.isEmpty()) { - role = context.getGuild().getRoleById(roleDb); - } - - context.reply("Current role needed to use music commands (default is everyone): %s", (role == null ? "Everyone" : role.getAsMention())); - context.reply("Reset role using 'conf music-role reset' or set it using 'conf musicrole @'"); - } - - if (args.size() == 2) { - if (context.getArgument(1).equals("reset")) { - if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE).isEmpty()) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null); - } - - context.reply("Successful reset of the role"); - } - - List roles = context.getMessage().getMentions().getRoles(); - if (roles.size() == 1) { - Role role = roles.get(0); - if (role.getGuild().getId().equals(context.getGuild().getId())) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId()); - context.reply("Successfully set %s as the role needed for music command", role.getAsMention()); - } - } - } - } - - // Role for music commands - if (context.getArgument(0).equals("music-channel")) { - if (args.size() == 1) { - String channelDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL); - TextChannel channel = null; - if (!channelDb.isEmpty()) { - channel = context.getGuild().getTextChannelById(channelDb); - } - - context.reply("Current channel where music commands can be executed (default is everywhere): %s", (channel == null ? "Everywhere" : channel.getAsMention())); - context.reply("Reset channel using 'conf music-channel reset' or set it using 'conf music-channel #'"); - } - - if (args.size() == 2) { - if (context.getArgument(1).equals("reset")) { - if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL).isEmpty()) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null); - } - - context.reply("Successful reset of the channel"); - } - - List channels = context.getMessage().getMentions().getChannels(TextChannel.class); - if (channels.size() == 1) { - TextChannel channel = channels.get(0); - if (channel.getGuild().getId().equals(context.getGuild().getId())) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId()); - context.reply("Successfully set %s as the channel required to execute music command", channel.getAsMention()); - } - } - } - } - - // Default volume for music player - if (context.getArgument(0).equals("music-volume")) { - if (args.size() == 1) { - int volume = Integer.parseInt(guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME)); - - context.reply("Default volume for music player: %d", volume); - context.reply("Set using 'conf music-volume '"); - } - - if (args.size() == 2) { - try { - int volume = Integer.parseInt(args.get(1)); - - if (volume > 0 && volume <= 100) { - guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume)); - MusicManager.get(context.getGuild()).setVolume(volume); - - context.reply("Default volume for music player is now %d", volume); - } else - throw new NumberFormatException(); - - } catch (NumberFormatException e) { - context.reply("Not a value between 0 and 100"); - } - } - } - - // Debug - if (context.getArgument(0).equals("debug")) { - if (args.size() == 1) { - boolean debug = guildManager.isDebugEnabled(); - - context.reply("Error logging is currently %s for this guild.", (debug ? "enabled" : "disabled")); - context.reply("Use 'conf debug 0/1' to toggle between states (0 = disabled, 1 = enabled)."); - } - - if (args.size() == 2) { - String str = args.get(1); - boolean debug = guildManager.isDebugEnabled(); - if (str.equals("0") || str.equalsIgnoreCase("disable")) { - debug = false; - } - - if (str.equals("1") || str.equalsIgnoreCase("enable")) { - debug = true; - } - - guildManager.setDebug(debug); - context.reply("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")); - - } - } - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - String sub = context.getEvent().getFullCommandName().split(" ")[1]; + public CommandExecutionResult execute(CommandContext context) { + String sub = context.getSubcommand(); GuildManager guildManager = GuildManager.getManager(context.getGuild()); context.ephemeral(); if ("music-role".equals(sub)) { - Role role = context.getEvent().getOption("role", OptionMapping::getAsRole); + Role role = context.getOption("role", CommandOption::getAsRole); assert role != null; if (role.getPosition() == -1) { @@ -216,7 +84,7 @@ public class ConfigureCommand extends AbstractAdminCommand { } if ("music-channel".equals(sub)) { - GuildChannelUnion channel = context.getEvent().getOption("channel", OptionMapping::getAsChannel); + GuildChannelUnion channel = context.getOption("channel", CommandOption::getAsChannel); if (channel == null) { guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null); @@ -235,7 +103,7 @@ public class ConfigureCommand extends AbstractAdminCommand { } if ("music-volume".equals(sub)) { - int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt); + int volume = context.getOption("volume", 30, CommandOption::getAsInt); guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume)); MusicManager.get(context.getGuild()).setVolume(volume); @@ -243,7 +111,7 @@ public class ConfigureCommand extends AbstractAdminCommand { } if ("error-logging".equals(sub)) { - boolean debug = context.getEvent().getOption("debug", true, OptionMapping::getAsBoolean); + boolean debug = context.getOption("debug", true, CommandOption::getAsBoolean); guildManager.setDebug(debug); return CommandExecutionResult.success("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")); diff --git a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java index 194828c..261d3b0 100644 --- a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java @@ -6,22 +6,20 @@ package me.bobert.netneutrality.command.crypto; */ import me.bobert.netneutrality.command.AbstractCommand; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.http.WebModule; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import org.json.JSONException; import org.json.JSONObject; import java.text.NumberFormat; import java.util.Currency; import java.util.Locale; -import java.util.Map; public class CryptoPriceCommand extends AbstractCommand { @@ -53,9 +51,9 @@ public class CryptoPriceCommand extends AbstractCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addOption(OptionType.STRING, "symbol", "The crypto symbol to fetch price for", true); - data.addOption(OptionType.STRING, "currency", "In which currency should the price be displayed"); + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addOption(OptionType.STRING, "symbol", "The crypto symbol to fetch price for", true); + builder.addOption(OptionType.STRING, "currency", "In which currency should the price be displayed"); } @Override @@ -64,59 +62,13 @@ public class CryptoPriceCommand extends AbstractCommand { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - Map args = context.getArgs(); - String currency = "USD"; - - if (args.isEmpty()) { - return CommandExecutionResult.warn("Usage: crypto "); - } - - if (args.size() == 2) { - try { - currency = Currency.getInstance(args.get(1).toUpperCase()).getCurrencyCode(); - } catch (IllegalArgumentException exception) { - return CommandExecutionResult.warn("Please enter a valid currency"); - } - } - - String crypto = args.get(0).toUpperCase(); - String finalCurrency = currency; - - context.async("Retreiving price, please wait...", asyncMessage -> { - String price; - Locale usa = new Locale("en", "US"); - NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa); - dollarFormat.setMaximumFractionDigits(24); - - try { - WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request(); - JSONObject json = http.getAsObject(); - if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) { - asyncMessage.edit("Crypto doesn't exist.. yet"); - return; - } - - price = dollarFormat.format(json.getDouble(finalCurrency)); - asyncMessage.edit("The current price of %s in %s is %s", crypto, finalCurrency, price); - - } catch (JSONException | IllegalArgumentException | ArithmeticException e) { - asyncMessage.edit("Error getting price of %s", crypto); - LogHandler.log(e); - } - }); - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { Locale usa = new Locale("en", "US"); NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa); dollarFormat.setMaximumFractionDigits(24); - String symbol = context.getEvent().getOption("symbol", OptionMapping::getAsString); - String currency = context.getEvent().getOption("currency", "USD", OptionMapping::getAsString); + String symbol = context.getOption("symbol", CommandOption::getAsString); + String currency = context.getOption("currency", "USD", CommandOption::getAsString).toUpperCase(); try { currency = Currency.getInstance(currency).getCurrencyCode(); diff --git a/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java index 33ed766..01f58d7 100644 --- a/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/git/ReportIssueCommand.java @@ -7,12 +7,12 @@ package me.bobert.netneutrality.command.git; import me.bobert.netneutrality.command.AbstractCommand; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.tasks.TaskManager; import me.bobert.netneutrality.tasks.git.ReportIssueTask; import me.bobert.netneutrality.utils.Settings; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class ReportIssueCommand extends AbstractCommand { @Override @@ -41,7 +41,7 @@ public class ReportIssueCommand extends AbstractCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @@ -51,7 +51,7 @@ public class ReportIssueCommand extends AbstractCommand { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { String gitlabHost = Settings.getInstance().get("GITLAB_HOST"); String gitlabToken = Settings.getInstance().get("GITLAB_TOKEN"); String projectId = Settings.getInstance().get("GITLAB_PROJECT_ID"); diff --git a/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java index f9202de..be70d67 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java @@ -8,11 +8,10 @@ package me.bobert.netneutrality.command.music; import me.bobert.netneutrality.command.AbstractMusicCommand; import me.bobert.netneutrality.command.CommandType; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class BitconnectCommand extends AbstractMusicCommand { @@ -32,7 +31,7 @@ public class BitconnectCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @@ -42,18 +41,10 @@ public class BitconnectCommand extends AbstractMusicCommand { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager player = MusicManager.get(context.getGuild()); - player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, true); - - context.deleteMessage(); - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ephemeral(); + context.deleteMessage(); manager.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, true); return CommandExecutionResult.success("Ready to invest?"); diff --git a/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java index 90f0950..5c1395d 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java @@ -7,11 +7,11 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class ClearQueueCommand extends AbstractMusicCommand { @@ -36,12 +36,12 @@ public class ClearQueueCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ack(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java index dd7a571..b640b3e 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java @@ -7,13 +7,12 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class CurrentlyPlayingCommand extends AbstractMusicCommand { @@ -33,27 +32,12 @@ public class CurrentlyPlayingCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - // no arguments + public void registerCommandOptions(CommandOptionsBuilder builder) { + } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - - if (MusicUtils.checkIfConnected(context)) { - if (!manager.isCurrentlyPlaying()) { - return CommandExecutionResult.info("Bot isn't playing any tracks right now"); - } - - context.reply(MessageFunction.buildCurrentlyPlayingMessage(manager)); - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ack(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java index 6096b14..414954f 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java @@ -7,11 +7,10 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class JoinCommand extends AbstractMusicCommand { @@ -31,21 +30,12 @@ public class JoinCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - if (MusicUtils.connect(context)) { - context.reply("Joining current voice channel"); - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { if (MusicUtils.connect(context)) { context.reply("Joining current voice channel"); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java index 311cb10..4d30b53 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java @@ -7,12 +7,11 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class LeaveCommand extends AbstractMusicCommand { @@ -32,24 +31,12 @@ public class LeaveCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - - if (MusicUtils.checkIfConnected(context)) { - context.reply("Leaving voice channel and clearing queue"); - manager.disconnect(); - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); if (MusicUtils.checkIfConnected(context)) { diff --git a/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java index d22c3e4..8d7b9de 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java @@ -7,13 +7,12 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class PlayCommand extends AbstractMusicCommand { @@ -33,26 +32,14 @@ public class PlayCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addOption(OptionType.STRING, "query", "URL or search query", true); + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addOption(OptionType.STRING, "query", "URL or search query", true); } @Override - public CommandExecutionResult execute(MessageCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - - if (context.getArgs().isEmpty()) { - return CommandExecutionResult.warn("Please specify a query"); - } - - manager.play(context.getMessageText(), context); - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - String query = context.getEvent().getOption("query", OptionMapping::getAsString); + String query = context.getOption("query", CommandOption::getAsString); context.ack(); manager.play(query, context); diff --git a/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java index b32e96e..3abddee 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java @@ -6,17 +6,17 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; public class QueueCommand extends AbstractMusicCommand { @@ -37,14 +37,8 @@ public class QueueCommand extends AbstractMusicCommand { } @Override - public DefaultMemberPermissions getSlashCommandPermission() { - return DefaultMemberPermissions.DISABLED; - } - - @Override - public void registerSlashCommandOptions(SlashCommandData data) { - - data.addSubcommands( + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addSubcommands( new SubcommandData("clear-all", "Clear all queued tracks"), new SubcommandData("delete-track", "Remove a single track from the queue") .addOptions(new OptionData(OptionType.INTEGER, "position", "Position of track", true) @@ -59,9 +53,14 @@ public class QueueCommand extends AbstractMusicCommand { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public DefaultMemberPermissions getSlashCommandPermission() { + return DefaultMemberPermissions.DISABLED; + } + + @Override + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); - String sub = context.getSubCommand(); + String sub = context.getSubcommand(); context.ack(); if (MusicUtils.checkIfConnected(context)) { @@ -72,7 +71,7 @@ public class QueueCommand extends AbstractMusicCommand { } case "delete-track" -> { - int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt); + int position = context.getOption("position", 0, CommandOption::getAsInt); return manager.getQueueManager().removeFromQueue(position); } @@ -83,7 +82,7 @@ public class QueueCommand extends AbstractMusicCommand { } case "from-user" -> { - Member user = context.getEvent().getOption("user", OptionMapping::getAsMember); + Member user = context.getOption("user", CommandOption::getAsMember); if (user == null) { return CommandExecutionResult.warn("The user isn't from this guild"); } @@ -92,7 +91,7 @@ public class QueueCommand extends AbstractMusicCommand { } case "remove-from-user" -> { - Member member = context.getEvent().getOption("user", OptionMapping::getAsMember); + Member member = context.getOption("user", CommandOption::getAsMember); if (member == null) { return CommandExecutionResult.warn("The user isn't from this guild"); } diff --git a/src/main/java/me/bobert/netneutrality/command/music/ResumeCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ResumeCommand.java index fcb1255..b151f3c 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ResumeCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ResumeCommand.java @@ -5,11 +5,11 @@ package me.bobert.netneutrality.command.music;/* */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class ResumeCommand extends AbstractMusicCommand { @Override @@ -28,12 +28,12 @@ public class ResumeCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); if (MusicUtils.checkIfConnected(context)) { diff --git a/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java index 69c75dd..48ca350 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java @@ -7,11 +7,11 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class ShuffleCommand extends AbstractMusicCommand { @Override @@ -30,12 +30,12 @@ public class ShuffleCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ack(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java index 1bf7443..5dca299 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java @@ -7,16 +7,15 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class SkipCommand extends AbstractMusicCommand { @@ -36,45 +35,23 @@ public class SkipCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addOptions(new OptionData(OptionType.INTEGER, "skip", "Number of tracks to skip") + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addOptions(new OptionData(OptionType.INTEGER, "skip", "Number of tracks to skip") .setMinValue(1)); } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - - if (!MusicUtils.checkIfConnected(context)) { - return CommandExecutionResult.none(); - } - - if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) { - if (UserFunction.isAdmin(context.getGuildUser()) && !context.getArgs().isEmpty()) { - try { - int skips = Integer.parseInt(context.getArgument(0)); - return manager.getQueueManager().skip(skips); - } catch (NumberFormatException e) { - context.reply("Please enter how many track(s) to skip"); - } - } else { - return manager.getQueueManager().skip(1); - } - } else { - return manager.voteSkip(context); - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ack(); if (MusicUtils.checkIfConnected(context)) { - int skips = context.getEvent().getOption("skip", 1, OptionMapping::getAsInt); - return manager.getQueueManager().skip(skips); + int skips = context.getOption("skip", 1, CommandOption::getAsInt); + if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context)) { + return manager.getQueueManager().skip(skips); + } else { + return manager.voteSkip(context); + } } return CommandExecutionResult.none(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java index 3d67d43..2a7741a 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java @@ -7,13 +7,12 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class StopCommand extends AbstractMusicCommand { @@ -33,17 +32,17 @@ public class StopCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); + context.ack(); if (MusicUtils.checkIfConnected(context)) { - // Need a resume command before - if (UserFunction.isAdmin(context.getGuildUser())) { + if (UserFunction.isAdmin(context)) { manager.stopTrack(); context.reply("Stopping current track"); } @@ -51,17 +50,4 @@ public class StopCommand extends AbstractMusicCommand { return CommandExecutionResult.none(); } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - context.ack(); - - if (MusicUtils.checkIfConnected(context)) { - manager.stopTrack(); - return CommandExecutionResult.success("Stopping music playback"); - } - - return CommandExecutionResult.none(); - } } diff --git a/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java index edb1d19..0302652 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java @@ -6,13 +6,12 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MessageFunction; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class ViewQueueCommand extends AbstractMusicCommand { @@ -32,28 +31,12 @@ public class ViewQueueCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - - if (MusicUtils.checkIfConnected(context)) { - int queueSize = manager.getQueueSize(); - if (queueSize > 0) { - context.reply(MessageFunction.buildViewQueueMessage(manager)); - } else { - context.reply("Queue is empty!"); - } - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); context.ack(); diff --git a/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java index 9d7fef2..29facff 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java @@ -7,16 +7,15 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.command.options.CommandOption; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; import me.bobert.netneutrality.utils.UserFunction; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class VolumeCommand extends AbstractMusicCommand { @@ -36,46 +35,23 @@ public class VolumeCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { - data.addOptions(new OptionData(OptionType.INTEGER, "volume", "Playback volume for this session", true) + public void registerCommandOptions(CommandOptionsBuilder builder) { + builder.addOptions(new OptionData(OptionType.INTEGER, "volume", "Playback volume for this session", true) .setRequiredRange(0, 100)); } @Override - public CommandExecutionResult execute(MessageCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); + int volume = context.getOption("volume", 30, CommandOption::getAsInt); - if (!MusicUtils.checkIfConnected(context)) - return CommandExecutionResult.none(); - - if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) { - try { - int volume = Integer.parseInt(context.getArgument(0)); - - if (volume > 0 && volume <= 100) { - manager.setVolume(volume); - context.reply("Volume set to %s", volume); - } else - throw new NumberFormatException(); - - } catch (NumberFormatException e) { - context.reply("Please enter a value from 0 to 100"); + if (MusicUtils.checkIfConnected(context)) { + if (UserFunction.isAdmin(context) || MusicUtils.isUserDj(context)) { + manager.setVolume(volume); + return CommandExecutionResult.success(String.format("Volume set to %s", volume)); } } return CommandExecutionResult.none(); } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt); - - if (MusicUtils.checkIfConnected(context)) { - manager.setVolume(volume); - return CommandExecutionResult.success(String.format("Volume set to %s", volume)); - } - - return CommandExecutionResult.none(); - } } diff --git a/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java index bcce618..1df001e 100644 --- a/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java @@ -6,12 +6,11 @@ package me.bobert.netneutrality.command.music; */ import me.bobert.netneutrality.command.AbstractMusicCommand; -import me.bobert.netneutrality.context.MessageCommandContext; -import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.command.CommandExecutionResult; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; +import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.music.MusicManager; import me.bobert.netneutrality.utils.MusicUtils; -import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; public class VoteSkipCommand extends AbstractMusicCommand { @@ -31,23 +30,12 @@ public class VoteSkipCommand extends AbstractMusicCommand { } @Override - public void registerSlashCommandOptions(SlashCommandData data) { + public void registerCommandOptions(CommandOptionsBuilder builder) { } @Override - public CommandExecutionResult execute(MessageCommandContext context) { - MusicManager manager = MusicManager.get(context.getGuild()); - - if (MusicUtils.checkIfConnected(context)) { - return manager.voteSkip(context); - } - - return CommandExecutionResult.none(); - } - - @Override - public CommandExecutionResult execute(SlashCommandContext context) { + public CommandExecutionResult execute(CommandContext context) { MusicManager manager = MusicManager.get(context.getGuild()); if (MusicUtils.checkIfConnected(context)) { diff --git a/src/main/java/me/bobert/netneutrality/context/AsyncContext.java b/src/main/java/me/bobert/netneutrality/context/AsyncContext.java index 056c9de..64da2b9 100644 --- a/src/main/java/me/bobert/netneutrality/context/AsyncContext.java +++ b/src/main/java/me/bobert/netneutrality/context/AsyncContext.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.context; import me.bobert.netneutrality.core.context.AbstractContext; import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.Event; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.components.LayoutComponent; @@ -38,6 +39,11 @@ public class AsyncContext extends AbstractContext { return null; } + @Override + public ChannelType getType() { + return ChannelType.UNKNOWN; + } + @Override public InteractionHook getInteractionHook() { return hook; @@ -59,7 +65,7 @@ public class AsyncContext extends AbstractContext { hook.deleteOriginal().queue(); } - if (message != null) { + if (message != null && getType() != ChannelType.PRIVATE) { message.delete().queue(); } } diff --git a/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java b/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java index f59e823..c4ae060 100644 --- a/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java +++ b/src/main/java/me/bobert/netneutrality/context/MessageCommandContext.java @@ -5,18 +5,26 @@ package me.bobert.netneutrality.context; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.command.options.CommandOption; +import me.bobert.netneutrality.core.command.options.MessageCommandOption; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.utils.Settings; -import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.components.LayoutComponent; import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -35,35 +43,9 @@ public class MessageCommandContext extends CommandContext { return event; } - public String getArgument(int index) { - return getArgs().get(index); - } - - public Map getArgs() { - Map args = new HashMap<>(); - - String msg = message.getContentDisplay().trim(); - msg = msg.substring(Settings.getInstance().getPrefix().length()).trim(); - String cmd = msg.split(" ", 2)[0]; - - String[] array = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split(" "); - for (int i = 0; i < array.length; i++) { - args.put(i, array[i]); - } - - if (args.size() == 1 && args.get(0).isEmpty()) { - args.remove(0); - } - - return args; - } - - public String getMessageText() { - String msg = message.getContentDisplay().trim(); - msg = msg.substring(Settings.getInstance().getPrefix().length()).trim(); - String cmd = msg.split(" ", 2)[0]; - - return msg.replaceFirst("(" + cmd + ")( )?", ""); + @Override + public ChannelType getType() { + return event.getChannelType(); } public Message getMessage() { @@ -102,11 +84,6 @@ public class MessageCommandContext extends CommandContext { } - @Override - public void replyEphemeral(MessageCreateData messageCreateData) { - sendMessageInPrivate(messageCreateData); - } - @Override public void ack(boolean ephemeral) { @@ -114,19 +91,206 @@ public class MessageCommandContext extends CommandContext { @Override public void deleteMessage() { - if (message != null) { + if (message != null && getType() != ChannelType.PRIVATE) { message.delete().queueAfter(1, TimeUnit.SECONDS); } } - public boolean isMessageFromOwner() { - return getGuildUser().isOwner(); + @Override + public List parseOptions(String[] args, List commandOptions) { + List options = new ArrayList<>(); + Mentions mentions = message.getMentions(); + int roleMentionIndex = 0; + int userMentionIndex = 0; + int memberMentionIndex = 0; + int channelMentionIndex = 0; + int index = 0; + + for (OptionData data : commandOptions) { + OptionType type = data.getType(); + Set requiredChannelTypes = data.getChannelTypes(); + List choices = data.getChoices(); + Object value = null; + + try { + switch (type) { + case STRING -> { + //TODO: check length + String str = args[index]; + + if (str.contains("<@") && str.contains(">")) { + invalidateContext(data, "expected a string at index " + index + " but a role/user/channel was provided"); + break; + } + + if (!choices.isEmpty()) { + List matched = choices.stream().filter(choice -> choice.getAsString().equalsIgnoreCase(str)).toList(); + if (matched.isEmpty()) { + invalidateContext(data, "not a valid choice, valid choices are: " + validChoices(choices)); + } + } + + Integer min = data.getMinLength(); + Integer max = data.getMaxLength(); + + if (min != null && str.length() < min) { + invalidateContext(data, "minimum length for argument is " + min); + } + + if (max != null && str.length() > max) { + invalidateContext(data, "maximum length for argument is " + max); + } + + value = str; + } + case INTEGER -> { + long num = Long.parseLong(args[index]); + if (!choices.isEmpty()) { + List matched = choices.stream().filter(choice -> choice.getAsLong() == num).toList(); + if (matched.isEmpty()) { + invalidateContext(data, "not a valid choice, valid choices are: " + validChoices(choices)); + } + } + value = num; + + Number max = data.getMaxValue(); + Number min = data.getMinValue(); + + if (min != null && num < min.longValue()) { + value = min.longValue(); + } else if (max != null && num > max.longValue()) { + value = max.longValue(); + } + + value = num; + } + case BOOLEAN -> value = Boolean.parseBoolean(args[index]); + case NUMBER -> { + double num = Long.parseLong(args[index]); + if (!choices.isEmpty()) { + List matched = choices.stream().filter(choice -> choice.getAsDouble() == num).toList(); + if (matched.isEmpty()) { + invalidateContext(data, "not a valid choice, valid choices are: " + validChoices(choices)); + break; + } + } + + Number max = data.getMaxValue(); + Number min = data.getMinValue(); + value = num; + + if (min != null && num < min.doubleValue()) { + value = min.doubleValue(); + } else if (max != null && num > max.doubleValue()) { + value = max.doubleValue(); + } + } + case ROLE -> { + if (mentions.getRoles().size() > roleMentionIndex) { + Role role = mentions.getRoles().get(roleMentionIndex++); + if (role.getGuild().getId().equals(getGuild().getId())) { + value = role; + } + + break; + } + + invalidateContext(data, "provided argument at index " + roleMentionIndex + " is not a valid role."); + } + case USER -> { + if (mentions.getMembers().size() > memberMentionIndex) { + Member member = mentions.getMembers().get(memberMentionIndex++); + if (member.getGuild().getId().equals(getGuild().getId())) { + value = member; + break; + } + } + + if (mentions.getUsers().size() > userMentionIndex) { + value = mentions.getUsers().get(userMentionIndex++); + break; + } + + invalidateContext(data, "provided argument is not a valid user."); + } + case MENTIONABLE -> { + if (mentions.getRoles().size() > roleMentionIndex) { + Role role = mentions.getRoles().get(roleMentionIndex++); + if (role.getGuild().getId().equals(getGuild().getId())) { + value = role; + } + + break; + } + + if (mentions.getMembers().size() > memberMentionIndex) { + Member member = mentions.getMembers().get(memberMentionIndex++); + if (member.getGuild().getId().equals(getGuild().getId())) { + value = member; + } + + break; + } + + if (mentions.getUsers().size() > userMentionIndex) { + value = mentions.getUsers().get(userMentionIndex++); + break; + } + + invalidateContext(data, "provided argument is not a valid user or role."); + } + case CHANNEL -> { + //TODO: check types + if (mentions.getChannels().size() > channelMentionIndex) { + GuildChannel channel = mentions.getChannels().get(channelMentionIndex++); + + if (!requiredChannelTypes.contains(channel.getType())) { + invalidateContext(data, "not a valid channel type at index " + channelMentionIndex + + " , valid types are: " + validTypes(requiredChannelTypes)); + break; + } + + if (channel.getGuild().getId().equals(getGuild().getId())) { + value = channel; + break; + } + } + + invalidateContext(data, "provided argument is not a valid channel."); + } + } + } catch (IndexOutOfBoundsException e) { + if (data.isRequired()) { + // This should never happen because required options are validated before calling parseOptions() + throw new IllegalArgumentException("Required option " + data.getName() + " was not found at index " + index); + } + } catch (NumberFormatException ignored) { + invalidateContext(data, "the argument provided was not a number."); + } + + options.add(new MessageCommandOption(data, value)); + index++; + } + + return options; } - public boolean isMessageFromCreator() { - String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER"); + private String validChoices(List choices) { + StringJoiner joiner = new StringJoiner(", "); + for (Command.Choice choice : choices) { + joiner.add(choice.getAsString()); + } - return creatorId.equals(getDiscordUser().getId()); + return joiner.toString(); + } + + private String validTypes(Set types) { + StringJoiner joiner = new StringJoiner(", "); + for (ChannelType type : types) { + joiner.add(type.name()); + } + + return joiner.toString(); } @Override @@ -145,11 +309,10 @@ public class MessageCommandContext extends CommandContext { } @Override - public String getSubCommand() { - if (!getArgs().isEmpty()) { - return getArgument(0); - } + public String getFullCommand() { + String rawMessage = message.getContentRaw().trim(); + String prefix = Settings.getInstance().getPrefix(); - return ""; + return rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim(); } } diff --git a/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java index 9745d12..bc4c532 100644 --- a/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java +++ b/src/main/java/me/bobert/netneutrality/context/MusicChannelContext.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.context; import me.bobert.netneutrality.core.context.AbstractContext; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.events.Event; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -28,6 +29,11 @@ public class MusicChannelContext extends AbstractContext { return null; } + @Override + public ChannelType getType() { + return getChannel().getType(); + } + @Override public InteractionHook getInteractionHook() { return null; diff --git a/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java b/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java index 3b1c2db..62fb94e 100644 --- a/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java +++ b/src/main/java/me/bobert/netneutrality/context/SlashCommandContext.java @@ -5,11 +5,18 @@ package me.bobert.netneutrality.context; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.core.command.options.CommandOption; +import me.bobert.netneutrality.core.command.options.SlashCommandOption; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.context.InteractionContext; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.modals.Modal; +import java.util.List; +import java.util.stream.Collectors; + public class SlashCommandContext extends CommandContext implements InteractionContext { private final SlashCommandInteractionEvent event; @@ -23,6 +30,11 @@ public class SlashCommandContext extends CommandContext implements InteractionCo return event; } + @Override + public ChannelType getType() { + return event.getChannelType(); + } + @Override public void deleteMessage() { // not implemented @@ -33,13 +45,28 @@ public class SlashCommandContext extends CommandContext implements InteractionCo event.replyModal(modal).queue(); } + @Override + public List parseOptions(String[] args, List commandOptions) { + return event.getOptions().stream().map(SlashCommandOption::new).collect(Collectors.toUnmodifiableList()); + } + @Override public String parseCommandName() { return event.getName(); } @Override - public String getSubCommand() { - return event.getFullCommandName().split(" ")[1]; + public String getFullCommand() { + return event.getFullCommandName(); + } + + @Override + public String getSubcommandGroup() { + return getFullCommand().split(" ")[2]; + } + + @Override + public String getSubcommand() { + return getFullCommand().split(" ")[1]; } } diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java index a8bbba2..30caaf9 100644 --- a/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java +++ b/src/main/java/me/bobert/netneutrality/context/tasks/ButtonTaskContext.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.context.tasks; import me.bobert.netneutrality.core.context.InteractionContext; import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; public class ButtonTaskContext extends TaskContext implements InteractionContext { @@ -22,6 +23,11 @@ public class ButtonTaskContext extends TaskContext implements InteractionContext return event; } + @Override + public ChannelType getType() { + return event.getChannelType(); + } + @Override public String getId() { return event.getComponentId(); diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java index a65f1f6..ac65383 100644 --- a/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java +++ b/src/main/java/me/bobert/netneutrality/context/tasks/EmojiTaskContext.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.context.tasks; import me.bobert.netneutrality.context.AsyncContext; import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.components.LayoutComponent; @@ -30,6 +31,11 @@ public class EmojiTaskContext extends TaskContext { return event; } + @Override + public ChannelType getType() { + return event.getChannelType(); + } + @Override public String getId() { return String.format("%s:%s", getGuild().getId(), event.getMessageId()); @@ -64,6 +70,10 @@ public class EmojiTaskContext extends TaskContext { @Override public void replyEphemeral(MessageCreateData messageCreateData) { + if (getType() == ChannelType.PRIVATE) { + return; + } + sendMessageInPrivate(messageCreateData); } diff --git a/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java b/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java index ce89a62..348de30 100644 --- a/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java +++ b/src/main/java/me/bobert/netneutrality/context/tasks/ModalTaskContext.java @@ -7,6 +7,7 @@ package me.bobert.netneutrality.context.tasks; import me.bobert.netneutrality.core.context.InteractionContext; import me.bobert.netneutrality.core.context.TaskContext; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; public class ModalTaskContext extends TaskContext implements InteractionContext { @@ -23,6 +24,11 @@ public class ModalTaskContext extends TaskContext implements InteractionContext return event; } + @Override + public ChannelType getType() { + return event.getChannelType(); + } + @Override public String getId() { return event.getModalId(); diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandContainer.java b/src/main/java/me/bobert/netneutrality/core/command/CommandContainer.java new file mode 100644 index 0000000..7437d3c --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandContainer.java @@ -0,0 +1,40 @@ +package me.bobert.netneutrality.core.command; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.command.CommandOptions; +import me.bobert.netneutrality.core.command.builder.CommandOptionsBuilder; + +public class CommandContainer { + public static final CommandContainer NULL_COMMAND = new CommandContainer(null); + private final Command command; + private CommandOptions commandOptions; + + public CommandContainer(Command command) { + this.command = command; + } + + public Command getCommand() { + return command; + } + + public CommandOptions getCommandOptions() { + return commandOptions; + } + + public void init() { + initOptions(); + } + + private void initOptions() { + if (command != null) { + CommandOptionsBuilder commandOptionsBuilder = new CommandOptionsBuilder(command); + command.registerCommandOptions(commandOptionsBuilder); + commandOptions = commandOptionsBuilder.build(); + } + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java index 0587d11..89ea011 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandManager.java @@ -6,18 +6,14 @@ package me.bobert.netneutrality.core.command; */ import me.bobert.netneutrality.command.Command; -import me.bobert.netneutrality.core.LogHandler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.interactions.commands.build.CommandData; -import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.reflections.Reflections; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; @@ -27,25 +23,26 @@ import java.util.Set; public class CommandManager { private static final Logger logger = LogManager.getLogger(CommandManager.class); - public static final HashMap commands = new HashMap<>(); + public static final HashMap commands = new HashMap<>(); public static void init(JDA jda) { logger.info("Loading commands.."); initCommands(); - registerSlashCommands(jda); logger.info("Loaded {} commands!", commands.size()); } public static Command getCommand(String commandString) { - return commands.get(commandString); + return getCommandContainer(commandString).getCommand(); + } + + public static CommandContainer getCommandContainer(String commandString) { + return commands.getOrDefault(commandString, CommandContainer.NULL_COMMAND); } public static void initCommands() { - Reflections reflections = new Reflections("me.bobert.netneutrality.command"); - Set> classes = reflections.getSubTypesOf(Command.class); - + Set> classes = new Reflections("me.bobert.netneutrality.command").getSubTypesOf(Command.class); for (Class s : classes) { try { if (Modifier.isAbstract(s.getModifiers())) { @@ -53,40 +50,45 @@ public class CommandManager { } Command command = s.getConstructor().newInstance(); - - if (!command.isEnabled()) + if (!command.isEnabled()) { continue; + } + + if (command.getCommandName() == null) { + throw new IllegalArgumentException("Command name is null"); + } + + if (command.getDescription() == null) { + throw new IllegalArgumentException("Command description is null"); + } + + CommandContainer container = new CommandContainer(command); + container.init(); if (!commands.containsKey(command.getCommandName())) { - commands.put(command.getCommandName(), command); + commands.put(command.getCommandName(), container); } - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException e) { - LogHandler.log(e); + } catch (Exception e) { + logger.error("Failed to instantiate command {}: {}", s.getSimpleName(), e.getMessage()); } } } - private static void registerSlashCommands(JDA jda) { + public static void registerSlashCommands(JDA jda) { CommandListUpdateAction commandListUpdateAction = jda.updateCommands(); List slashCommands = new ArrayList<>(); - for (Command command : commands.values()) { + for (CommandContainer container : commands.values()) { + Command command = container.getCommand(); if (!command.isEnabled() || !command.isSlashCommand()) { continue; } - logger.debug("Registering slash command: {}", command.getCommandName()); - String description = command.getDescription(); - if (StringUtils.isBlank(description)) { - description = "No description provided for this command"; - } try { - SlashCommandData data = Commands.slash(command.getCommandName(), description); + SlashCommandData data = container.getCommandOptions().getSlashCommandData(); data.setDefaultPermissions(command.getSlashCommandPermission()); data.setGuildOnly(command.isGuildOnly()); - command.registerSlashCommandOptions(data); slashCommands.add(data); } catch (Exception e) { diff --git a/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java index 8584059..4c8bc76 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java +++ b/src/main/java/me/bobert/netneutrality/core/command/CommandProcessor.java @@ -7,15 +7,26 @@ package me.bobert.netneutrality.core.command; import io.sentry.Sentry; import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.command.CommandOptions; import me.bobert.netneutrality.context.MessageCommandContext; import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.LogHandler; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.exceptions.CommandExecutionException; import me.bobert.netneutrality.utils.UserFunction; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.util.function.Predicate.not; import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.ERROR; import static me.bobert.netneutrality.core.command.CommandExecutionResult.Result.NOACTION; @@ -33,14 +44,16 @@ public class CommandProcessor { return; } - Command command = CommandManager.getCommand(context.parseCommandName()); - context.setCommand(command); - + CommandContainer container = CommandManager.getCommandContainer(context.parseCommandName()); + Command command = container.getCommand(); if (command == null) { throw new IllegalArgumentException("Context has no command"); } + context.setCommand(command); if (command.isGuildOnly() && !context.isFromGuild()) { + context.replyEphemeral("This command is only available in a guild"); + context.deleteMessage(); return; } @@ -49,53 +62,149 @@ public class CommandProcessor { } try { - logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage": context.getGuild().getId()); + logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild() == null ? "PrivateMessage" : context.getGuild().getId()); configureSentry(command); - if (context instanceof MessageCommandContext && command.needAdmin() && !UserFunction.isAdmin(context.getGuildUser())) { + if (!canExecute(command)) { context.reply("You are not powerful enough, scrub"); return; } - if (command.canBeExecutedBy(context.getGuildUser())) { - if (command.canBeExecutedFrom(context.getChannel())) { - CommandExecutionResult commandExecutionResult = null; - - if (context instanceof SlashCommandContext slashCommandContext) { - commandExecutionResult = command.execute(slashCommandContext); - } - - if (context instanceof MessageCommandContext messageCommandContext) { - commandExecutionResult = command.execute(messageCommandContext); - } - - if (commandExecutionResult == null) { - throw new RuntimeException("CommandExecutionResult is null"); - } - - CommandExecutionResult.Result result = commandExecutionResult.getResult(); - String message = commandExecutionResult.getMessage(); - if (result == NOACTION) { - return; - } - - if (result == ERROR) { - Exception exception = commandExecutionResult.getException(); - logger.error("Error from command {}: {}", command.getCommandName(), message, exception); - } - - context.reply(message); - } + if (!checkUsage(container.getCommandOptions())) { + return; } - //TODO: make better - } catch (CommandExecutionException exception) { - LogHandler.log(exception); + if (!context.isValid()) { + StringBuilder reply = new StringBuilder("Invalid arguments provided for this command:\n"); + for (Map.Entry invalid : context.getInvalidOptions()) { + reply.append("- ").append(invalid.getKey().getName()).append(": ").append(invalid.getValue()).append("\n"); + } + + context.reply(reply.toString()); + return; + } + + CommandExecutionResult commandExecutionResult = command.execute(context); + if (commandExecutionResult == null) { + throw new RuntimeException("CommandExecutionResult is null"); + } + + CommandExecutionResult.Result result = commandExecutionResult.getResult(); + if (result == NOACTION) { + return; + } + + String message = commandExecutionResult.getMessage(); + if (result == ERROR) { + logger.error("Error from command {}: {}", command.getCommandName(), message, commandExecutionResult.getException()); + } + + context.reply(message); } catch (Exception exception) { + if (exception instanceof CommandExecutionException) { + LogHandler.log(exception); + } + LogHandler.log(new CommandExecutionException("Error during command execution", context, exception)); } } + private boolean checkUsage(CommandOptions commandOptions) { + String[] possibleCommands = Arrays.stream(context.getFullCommand().split(" ")).skip(1).toArray(String[]::new); + if (possibleCommands.length == 0) { + if (!commandOptions.getSubcommandGroups().isEmpty()) { + context.reply("Invalid usage for command: missing subcommand.\n" + + "Use **/help %s** for information on how to use this command", context.getCommand().getCommandName()); + return false; + } + + if (!commandOptions.getSubcommands().isEmpty()) { + context.reply("Invalid usage for command: missing subcommand.\n" + + "Use **/help %s** for information on how to use this command", context.getCommand().getCommandName()); + return false; + } + + return checkOptions(possibleCommands, 0, commandOptions.getOptions()); + } + + SubcommandGroupData group = commandOptions.getSubcommandGroups().stream().filter(data -> data.getName().equals(possibleCommands[0])).findFirst().orElse(null); + if (group != null) { + if (possibleCommands.length < 2) { + context.reply("Invalid usage for command: missing subcommand.\n" + + "Use **/help %s** for information on how to use this command", context.getCommand().getCommandName()); + return false; // missing subcommand + } + + SubcommandData subcommand = group.getSubcommands().stream().filter(data -> data.getName().equals(possibleCommands[1])).findFirst().orElse(null); + if (subcommand != null) { + context.setSubcommandGroup(group); + context.setSubcommand(subcommand); + return checkOptions(possibleCommands, 2, subcommand.getOptions()); + } + } + + SubcommandData subcommand = commandOptions.getSubcommands().stream().filter(data -> data.getName().equals(possibleCommands[0])).findFirst().orElse(null); + if (subcommand != null) { + context.setSubcommand(subcommand); + return checkOptions(possibleCommands, 1, subcommand.getOptions()); + } + + return checkOptions(possibleCommands, 0, commandOptions.getOptions()); + } + + private boolean checkOptions(String[] args, int skip, List options) { + if (context instanceof SlashCommandContext) { + context.setCurrentCommandOptions(args, options); + return true; // slash commands are validated by discord + } + + List arguments = new ArrayList<>(Arrays.stream(args).skip(skip).toList()); + int required = options.stream().filter(OptionData::isRequired).toList().size(); + int optional = options.stream().filter(not(OptionData::isRequired)).toList().size(); + + if (arguments.size() < required) { + context.reply("Invalid usage for command: missing one or more required arguments\n" + + "Use **/help %s** for information on how to use this command", context.getCommand().getCommandName()); + return false; + } + + // check if last option is a string, if it is then combine all remaining strings into one + // ["@user", "you", "are", "banned"] becomes ["@user", "you are banned"] + if (!options.isEmpty() && arguments.size() > 1) { + OptionData last = options.get(options.size() - 1); + if (last.getType() == OptionType.STRING) { + List lastArgs = arguments.stream().skip(options.size() -1).toList(); + arguments.removeAll(lastArgs); + arguments.add(String.join(" ", lastArgs)); + } + } + + if (arguments.size() > required + optional) { + context.reply("Invalid usage for command: too many arguments\n" + + "Use **/help %s** for information on how to use this command", context.getCommand().getCommandName()); + return false; + } + + context.setCurrentCommandOptions(arguments.toArray(String[]::new), options); + return true; + } + + private boolean canExecute(Command command) { + if (command.canBeExecutedFrom(context.getChannel())) { + return true; + } + + if (context instanceof SlashCommandContext) { + return true; // no further check since slash perms are handled by guild settings + } + + if (command.canBeExecutedBy(context.getGuildUser())) { + return true; + } + + return command.needAdmin() && UserFunction.isAdmin(context.getGuildUser()); + } + private void configureSentry(Command command) { Sentry.configureScope(scope -> { scope.setTag("command", command.getCommandName()); diff --git a/src/main/java/me/bobert/netneutrality/core/command/builder/CommandOptionsBuilder.java b/src/main/java/me/bobert/netneutrality/core/command/builder/CommandOptionsBuilder.java new file mode 100644 index 0000000..801b681 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/command/builder/CommandOptionsBuilder.java @@ -0,0 +1,24 @@ +package me.bobert.netneutrality.core.command.builder; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.command.CommandOptions; +import net.dv8tion.jda.internal.interactions.CommandDataImpl; + +public class CommandOptionsBuilder extends CommandDataImpl { + private final Command command; + + public CommandOptionsBuilder(Command command) { + super(command.getCommandName(), command.getDescription()); + this.command = command; + } + + public CommandOptions build() { + //TODO: add more command types (slash, context menu), constructors are not the same so we can't extend CommandDataImpl + return new CommandOptions(command, this); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/command/options/CommandOption.java b/src/main/java/me/bobert/netneutrality/core/command/options/CommandOption.java new file mode 100644 index 0000000..0a17ae2 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/command/options/CommandOption.java @@ -0,0 +1,164 @@ +package me.bobert.netneutrality.core.command.options; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +/** + * This is a wrapper around {@link OptionData} that can support both slash and message commands + */ +public interface CommandOption { + OptionType getType(); + + /** + * The name of this option. + * + * @return The option name + */ + String getName(); + + /** + * The file uploaded for this option. + *
This is represented as an {@link Message.Attachment#isEphemeral() ephemeral} attachment which will only be hosted for up to 2 weeks. + * If you want a permanent reference, you must download it. + * + * @throws IllegalStateException + * If this option {@link #getType() type} is not {@link OptionType#ATTACHMENT} + * + * @return {@link net.dv8tion.jda.api.entities.Message.Attachment Attachment} + */ + Message.Attachment getAsAttachment(); + + /** + * The String representation of this option value. + *
This will automatically convert the value to a string if the type is not {@link OptionType#STRING OptionType.STRING}. + *
This will be the ID of any resolved entity such as {@link Role} or {@link Member}. + * + * @return The String representation of this option value + */ + String getAsString(); + + /** + * The boolean value. + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#BOOLEAN BOOLEAN} + * + * @return The boolean value + */ + boolean getAsBoolean(); + + /** + * The long value for this option. + *
This will be the ID of any resolved entity such as {@link Role} or {@link Member}. + * + * @throws IllegalStateException + * If this option {@link #getType() type} cannot be converted to a long + * @throws NumberFormatException + * If this option is of type {@link OptionType#STRING STRING} and could not be parsed to a valid long value + * + * @return The long value + */ + long getAsLong(); + + /** + * The int value for this option. + *
This will be the ID of any resolved entity such as {@link Role} or {@link Member}. + * + *

It is highly recommended to assert int values by using {@link OptionData#setRequiredRange(long, long)} + * + * @throws IllegalStateException + * If this option {@link #getType() type} cannot be converted to a long + * @throws NumberFormatException + * If this option is of type {@link OptionType#STRING STRING} and could not be parsed to a valid long value + * @throws ArithmeticException + * If the provided integer value cannot fit into a 32bit signed int + * + * @return The int value + */ + int getAsInt(); + + /** + * The double value for this option. + * + * @throws IllegalStateException + * If this option {@link #getType() type} cannot be converted to a double + * @throws NumberFormatException + * If this option is of type {@link OptionType#STRING STRING} and could not be parsed to a valid double value + * + * @return The double value + */ + double getAsDouble(); + + /** + * The resolved {@link IMentionable} instance for this option value. + * + * @throws IllegalStateException + * If the mentioned entity is not resolvable + * + * @return The resolved {@link IMentionable} + */ + IMentionable getAsMentionable(); + + /** + * The resolved {@link Member} for this option value. + *
Note that {@link OptionType#USER OptionType.USER} can also accept users that are not members of a guild, in which case this will be null! + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#USER USER} or {@link OptionType#MENTIONABLE MENTIONABLE} + * + * @return The resolved {@link Member}, or null + */ + Member getAsMember(); + + /** + * The resolved {@link User} for this option value. + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#USER USER} or + * {@link OptionType#MENTIONABLE MENTIONABLE} without a resolved user + * + * @return The resolved {@link User} + */ + User getAsUser(); + + /** + * The resolved {@link Role} for this option value. + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#ROLE ROLE} or + * {@link OptionType#MENTIONABLE MENTIONABLE} without a resolved role + * + * @return The resolved {@link Role} + */ + Role getAsRole(); + + /** + * The {@link ChannelType} for the resolved channel. + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#CHANNEL CHANNEL} + * + * @return The {@link ChannelType} + */ + ChannelType getChannelType(); + + /** + * The resolved {@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel} for this option value. + *
Note that {@link OptionType#CHANNEL OptionType.CHANNEL} can accept channels of any type! + * + * @throws IllegalStateException + * If this option is not of type {@link OptionType#CHANNEL CHANNEL} + * or could not be resolved for unexpected reasons + * + * @return The resolved {@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel} + */ + GuildChannelUnion getAsChannel(); +} diff --git a/src/main/java/me/bobert/netneutrality/core/command/options/MessageCommandOption.java b/src/main/java/me/bobert/netneutrality/core/command/options/MessageCommandOption.java new file mode 100644 index 0000000..bc248d8 --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/command/options/MessageCommandOption.java @@ -0,0 +1,149 @@ +package me.bobert.netneutrality.core.command.options; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +public class MessageCommandOption implements CommandOption { + private final OptionData data; + private final OptionType type; + private final Object value; + + public MessageCommandOption(OptionData data, Object value) { + this.data = data; + this.type = data.getType(); + this.value = value; + } + + @Override + public OptionType getType() { + return data.getType(); + } + + @Override + public String getName() { + return data.getName(); + } + + @Override + public Message.Attachment getAsAttachment() { + throw new UnsupportedOperationException("Not supported for message commands"); + } + + @Override + public String getAsString() { + return switch (type) { + case STRING -> (String) value; + case BOOLEAN -> String.valueOf((boolean) value); + case NUMBER -> String.valueOf((double) value); + case MENTIONABLE -> ((IMentionable) value).getId(); + case CHANNEL -> ((GuildChannelUnion) value).getId(); + case ROLE -> ((Role) value).getId(); + case USER -> ((User) value).getId(); + case INTEGER -> String.valueOf((long) value); + case ATTACHMENT -> ((Message.Attachment) value).getId(); + default -> throw new IllegalStateException("Cannot convert option of type " + type + " to long"); + }; + } + + @Override + public boolean getAsBoolean() { + if (type != OptionType.BOOLEAN) { + throw new IllegalStateException("Cannot convert option of type " + type + " to boolean"); + } + + return (boolean) value; + } + + @Override + public long getAsLong() { + return switch (type) { + case STRING -> Long.parseLong((String) value); + case MENTIONABLE -> ((IMentionable) value).getIdLong(); + case CHANNEL -> ((GuildChannelUnion) value).getIdLong(); + case ROLE -> ((Role) value).getIdLong(); + case USER -> ((User) value).getIdLong(); + case INTEGER -> (long) value; + case NUMBER -> ((Double) value).longValue(); + case ATTACHMENT -> ((Message.Attachment) value).getIdLong(); + default -> throw new IllegalStateException("Cannot convert option of type " + type + " to long"); + }; + } + + @Override + public int getAsInt() { + return Math.toIntExact(getAsLong()); + } + + @Override + public double getAsDouble() { + return switch (type) { + case STRING -> Double.parseDouble((String) value); + case NUMBER -> (double) value; + case INTEGER -> (int) value; + default -> throw new IllegalStateException("Cannot convert option of type " + type + " to double"); + }; + } + + @Override + public IMentionable getAsMentionable() { + if (value instanceof IMentionable) { + return (IMentionable) value; + } + + throw new IllegalStateException("Cannot resolve option of type " + type + " to IMentionable"); + } + + @Override + public Member getAsMember() { + if (value instanceof Member) { + return (Member) value; + } + + if (value instanceof User) { + return null; + } + + throw new IllegalStateException("Cannot resolve option of type " + type + " to Member"); + } + + @Override + public User getAsUser() { + if (value instanceof User) { + return (User) value; + } + + throw new IllegalStateException("Cannot resolve option of type " + type + " to User"); + } + + @Override + public Role getAsRole() { + if (value instanceof Role) { + return (Role) value; + } + + throw new IllegalStateException("Cannot resolve option of type " + type + " to Role"); + } + + @Override + public ChannelType getChannelType() { + return getAsChannel().getType(); + } + + @Override + public GuildChannelUnion getAsChannel() { + if (value instanceof GuildChannelUnion) { + return (GuildChannelUnion) value; + } + + throw new IllegalStateException("Cannot resolve option of type " + type + " to GuildChannelUnion"); + } + +} diff --git a/src/main/java/me/bobert/netneutrality/core/command/options/SlashCommandOption.java b/src/main/java/me/bobert/netneutrality/core/command/options/SlashCommandOption.java new file mode 100644 index 0000000..90a8c0d --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/core/command/options/SlashCommandOption.java @@ -0,0 +1,90 @@ +package me.bobert.netneutrality.core.command.options; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; + +public class SlashCommandOption implements CommandOption { + private final OptionMapping mapping; + + public SlashCommandOption(OptionMapping mapping) { + this.mapping = mapping; + } + + @Override + public OptionType getType() { + return mapping.getType(); + } + + @Override + public String getName() { + return mapping.getName(); + } + + @Override + public Message.Attachment getAsAttachment() { + return mapping.getAsAttachment(); + } + + @Override + public String getAsString() { + return mapping.getAsString(); + } + + @Override + public boolean getAsBoolean() { + return mapping.getAsBoolean(); + } + + @Override + public long getAsLong() { + return mapping.getAsLong(); + } + + @Override + public int getAsInt() { + return mapping.getAsInt(); + } + + @Override + public double getAsDouble() { + return mapping.getAsDouble(); + } + + @Override + public IMentionable getAsMentionable() { + return mapping.getAsMentionable(); + } + + @Override + public Member getAsMember() { + return mapping.getAsMember(); + } + + @Override + public User getAsUser() { + return mapping.getAsUser(); + } + + @Override + public Role getAsRole() { + return mapping.getAsRole(); + } + + @Override + public ChannelType getChannelType() { + return mapping.getChannelType(); + } + + @Override + public GuildChannelUnion getAsChannel() { + return mapping.getAsChannel(); + } +} diff --git a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java index 42a82df..e58739a 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java +++ b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java @@ -6,17 +6,32 @@ package me.bobert.netneutrality.core.context; */ import me.bobert.netneutrality.command.Command; +import me.bobert.netneutrality.core.command.options.CommandOption; +import me.bobert.netneutrality.utils.Settings; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandData; +import net.dv8tion.jda.api.interactions.commands.build.SubcommandGroupData; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; public abstract class CommandContext extends AbstractContext { private Command command; + private SubcommandGroupData subcommandGroup; + private SubcommandData subcommand; + private final Map invalidOptions = new HashMap<>(); + private final List options; public CommandContext(JDA jda, Guild guild, MessageChannel channel, Member member, User user) { super(jda, guild, channel, member, user); + this.options = new ArrayList<>(); } public Command getCommand() { @@ -27,8 +42,97 @@ public abstract class CommandContext extends AbstractContext { this.command = command; } + public void setSubcommandGroup(SubcommandGroupData subcommandGroup) { + this.subcommandGroup = subcommandGroup; + } + + public void setSubcommand(SubcommandData subcommand) { + this.subcommand = subcommand; + } + + public void setCurrentCommandOptions(String[] args, List options) { + this.options.clear(); + this.options.addAll(parseOptions(args, options)); + } + + protected void invalidateContext(OptionData option, String message) { + invalidOptions.put(option, message); + } + + public boolean isValid() { + return invalidOptions.isEmpty(); + } + + public Set> getInvalidOptions() { + return invalidOptions.entrySet(); + } + + public T getOption(String name, Supplier fallback, Function resolver) { + Objects.requireNonNull(resolver, "resolver cannot be null"); + CommandOption mapping = getOption(name); + if (mapping != null) + return resolver.apply(mapping); + return fallback == null ? null : fallback.get(); + } + + public T getOption(String name, T fallback, Function resolver) { + Objects.requireNonNull(resolver, "resolver cannot be null"); + CommandOption mapping = getOption(name); + if (mapping != null) + return resolver.apply(mapping); + return fallback; + } + + public T getOption(String name, Function resolver) { + return getOption(name, null, resolver); + } + + public CommandOption getOption(String name) { + List options = getOptionsByName(name); + return options.isEmpty() ? null : options.get(0); + } + + public List getOptionsByName(String name) { + Objects.requireNonNull(name, "name cannot be null"); + return options.stream().filter(opt -> opt.getName().equals(name)).collect(Collectors.toList()); + } + + public String getSubcommandGroup() { + if (subcommandGroup == null) { + return null; + } + + return subcommandGroup.getName(); + } + + public String getSubcommand() { + if (subcommand == null) { + return null; + } + + return subcommand.getName(); + } + + public boolean isMessageFromOwner() { + if (getGuildUser() == null) { + return false; + } + + return getGuildUser().isOwner(); + } + + public boolean isMessageFromCreator() { + String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER"); + + return creatorId.equals(getDiscordUser().getId()); + } + + public abstract List parseOptions(String[] args, List commandOptions); + public abstract String parseCommandName(); - public abstract String getSubCommand(); + public abstract String getFullCommand(); + + } diff --git a/src/main/java/me/bobert/netneutrality/core/context/Context.java b/src/main/java/me/bobert/netneutrality/core/context/Context.java index b13ddf6..bd0ad93 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/Context.java +++ b/src/main/java/me/bobert/netneutrality/core/context/Context.java @@ -7,11 +7,10 @@ package me.bobert.netneutrality.core.context; import me.bobert.netneutrality.context.AsyncContext; import me.bobert.netneutrality.core.GuildManager; +import me.bobert.netneutrality.utils.ContextUtils; import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.Event; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -26,6 +25,8 @@ import java.util.function.Consumer; public interface Context { Event getEvent(); + ChannelType getType(); + JDA getJDA(); InteractionHook getInteractionHook(); @@ -73,7 +74,7 @@ public interface Context { } default void reply(String message) { - reply(MessageCreateData.fromContent(message)); + replyWithLimit(message); } default void reply(MessageEmbed embed) { @@ -85,7 +86,7 @@ public interface Context { } default void reply(String format, Object... arguments) { - reply(String.format(format, arguments)); + replyWithLimit(String.format(format, arguments)); } default void edit(MessageEditData messageEditData, boolean ephemeral, List components, Consumer consumer) { @@ -165,7 +166,7 @@ public interface Context { } default void sendMessageInPrivate(String message) { - sendMessageInPrivate(message, List.of()); + sendMessageInPrivateWithLimit(message); } default void sendMessageInPrivate(String message, List components) { @@ -179,4 +180,16 @@ public interface Context { default void sendMessageInPrivate(List embeds) { sendMessageInPrivate(MessageCreateData.fromEmbeds(embeds)); } + + private void replyWithLimit(String message) { + for (String chunk : ContextUtils.splitMessage(message, Message.MAX_CONTENT_LENGTH)) { + reply(MessageCreateData.fromContent(chunk)); + } + } + + private void sendMessageInPrivateWithLimit(String message) { + for (String chunk : ContextUtils.splitMessage(message, Message.MAX_CONTENT_LENGTH)) { + sendMessageInPrivate(MessageCreateData.fromContent(chunk)); + } + } } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java b/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java index 686a5e4..2803bc7 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/BuiltInTask.java @@ -5,19 +5,18 @@ package me.bobert.netneutrality.core.tasks; * https://discord.gg/qUa6zNFZsd */ -import java.lang.reflect.InvocationTargetException; - /** * Represents a task that has a known ID and is loaded at runtime. * This can be any task and is not necessarily a button. */ public interface BuiltInTask extends Task { + static TaskButtonCreator createButton(Class clazz) { - try { - BuiltInTask task = clazz.getConstructor().newInstance(); - return new TaskButtonCreator(task.getId()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException(e); + BuiltInTask builtInTask = TaskManager.getInstance().getTaskMap().values().stream().filter(clazz::isInstance).map(task -> (BuiltInTask) task).findFirst().orElse(null); + if (builtInTask == null) { + throw new IllegalArgumentException("Task class " + clazz.getName() + " was not registered by the task manager."); } + + return new TaskButtonCreator(builtInTask.getId()); } } diff --git a/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java index 3692be2..cf190b1 100644 --- a/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java +++ b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java @@ -1,5 +1,4 @@ package me.bobert.netneutrality.exceptions; - /* * Copyright (c) anthorob * @@ -8,9 +7,6 @@ package me.bobert.netneutrality.exceptions; import me.bobert.netneutrality.core.context.CommandContext; -/** - * Logs the error internally, without sending a message - */ public class CommandExecutionException extends ContextException { public CommandExecutionException(String message, CommandContext context, Throwable cause) { diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java b/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java index 5b78d72..d9c8238 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/BotInfo.java @@ -17,7 +17,7 @@ public class BotInfo extends AbstractBuiltInTask { @Override public void execute(TaskContext context) { - context.reply("For information about the bot and to follow the development, " + + context.sendMessageInPrivate("For information about the bot and to follow the development, " + "you can join the discord here: https://discord.gg/udVJwTgV"); } } diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java b/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java index f6bd2d5..3206ecc 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/CommandHelp.java @@ -8,6 +8,7 @@ package me.bobert.netneutrality.tasks.help; import me.bobert.netneutrality.core.context.TaskContext; import me.bobert.netneutrality.core.tasks.AbstractBuiltInTask; import me.bobert.netneutrality.utils.MessageFunction; +import me.bobert.netneutrality.utils.Settings; public class CommandHelp extends AbstractBuiltInTask { @@ -18,6 +19,7 @@ public class CommandHelp extends AbstractBuiltInTask { @Override public void execute(TaskContext context) { - context.reply(MessageFunction.buildHelpMessage()); + context.replyEphemeral("The prefix for the bot is " + Settings.getInstance().getPrefix()); + context.replyEphemeral(MessageFunction.buildHelpMessage()); } } diff --git a/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java b/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java index d903675..3c643c7 100644 --- a/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java +++ b/src/main/java/me/bobert/netneutrality/tasks/help/ReportBug.java @@ -17,7 +17,7 @@ public class ReportBug extends AbstractBuiltInTask { @Override public void execute(TaskContext context) { - context.reply("To report a buuug with the bot, you can either use the /report-bug command " + + context.sendMessageInPrivate("To report a bug with the bot, you can either use the /report-bug command " + "or join the discord here: https://discord.gg/udVJwTgV"); } } diff --git a/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java index 596cc7b..2efb483 100644 --- a/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java +++ b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java @@ -30,10 +30,10 @@ public class MusicPlayerAutoDisconnectThread implements Runnable { } private void processManager(MusicManager manager) throws InterruptedException { - logger.debug("Processing MusicPlayerAutoDisconnectThread for guild {}", manager.getGuild().getId()); AudioChannel channel = manager.getGuild().getAudioManager().getConnectedChannel(); if (channel != null) { + logger.debug("Processing MusicPlayerAutoDisconnectThread for guild {}", manager.getGuild().getId()); if (conditionsMetToDisconnect(manager)) { logger.debug("Guild {}: Sleeping 30 seconds and checking again", manager.getGuild().getId()); // Sleep 30 seconds then check again (prevents disconnecting right after a track ends) diff --git a/src/main/java/me/bobert/netneutrality/utils/ContextUtils.java b/src/main/java/me/bobert/netneutrality/utils/ContextUtils.java new file mode 100644 index 0000000..059b6fe --- /dev/null +++ b/src/main/java/me/bobert/netneutrality/utils/ContextUtils.java @@ -0,0 +1,78 @@ +package me.bobert.netneutrality.utils; +/* + * Copyright (c) anthorob + * + * https://discord.gg/qUa6zNFZsd + */ + +import java.util.ArrayList; +import java.util.List; + +public class ContextUtils { + + + public static List splitMessage(String input, int limit) { + List chunks = new ArrayList<>(); + int index = 0; + + while (index < input.length()) { + // Check if the remaining text is within the limit + if (input.length() - index <= limit) { + chunks.add(trimTrailingNewline(input.substring(index))); + break; + } + + // Determine the endpoint of the current chunk + int endIndex = Math.min(index + limit, input.length()); + String chunk = input.substring(index, endIndex); + + // Find the last suitable truncation boundary + int boundaryIndex = findLastBoundary(chunk); + + // Adjust the truncation index + if (boundaryIndex == -1 && endIndex < input.length()) { + // Hard truncate if no suitable boundary is found and not at the end + boundaryIndex = limit; + } else if (boundaryIndex > -1) { + // Include sentence-ending punctuation if present + boundaryIndex += 1; + } + + // Extract the chunk and trim unnecessary trailing '\n' + endIndex = index + (boundaryIndex > -1 ? boundaryIndex : limit); + String trimmedChunk = trimTrailingNewline(input.substring(index, endIndex)); + + // Add the chunk to the result list + chunks.add(trimmedChunk); + + // Move the index to the next chunk, skipping redundant newlines + index = endIndex; + while (index < input.length() && input.charAt(index) == '\n') { + index++; + } + } + + return chunks; + } + + private static int findLastBoundary(String chunk) { + // Find the last sentence-ending punctuation or space within the chunk + int lastSentenceBoundary = Math.max( + Math.max(chunk.lastIndexOf("."), Math.max(chunk.lastIndexOf("!"), chunk.lastIndexOf("?"))), + chunk.lastIndexOf("\n") + ); + int lastSpace = chunk.lastIndexOf(" "); + return Math.max(lastSentenceBoundary, lastSpace); + } + + private static String trimTrailingNewline(String chunk) { + // Trim trailing '\n' if present + if (chunk.endsWith("\n")) { + return chunk.substring(0, chunk.length() - 1).trim(); + } + + return chunk.trim(); + } + + +} diff --git a/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java index 2c5efe6..778881d 100644 --- a/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java +++ b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java @@ -12,6 +12,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; import io.sentry.Sentry; import me.bobert.netneutrality.command.Command; import me.bobert.netneutrality.command.CommandType; +import me.bobert.netneutrality.core.command.CommandContainer; import me.bobert.netneutrality.core.command.CommandManager; import me.bobert.netneutrality.core.context.CommandContext; import me.bobert.netneutrality.core.context.Context; @@ -24,7 +25,6 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import org.apache.commons.lang3.time.DurationFormatUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -203,7 +203,8 @@ public class MessageFunction { public static String buildHelpMessage() { Map help = new HashMap<>(); - for (Command command : CommandManager.commands.values()) { + for (CommandContainer container : CommandManager.commands.values()) { + Command command = container.getCommand(); if (command.getType() == null) { continue; } @@ -233,19 +234,19 @@ public class MessageFunction { String subtext = "\n\n" + "-# The bot supports both types of commands: slash (/) and message ($), " + - "however not all slash commands might have a corresponding message command." + + "however not all slash commands might have a corresponding message command.\n" + "-# To get help for a specific command, please use /help "; return String.join("\n", help.values()) + subtext; } - public static String buildCommandHelpMessage(Command command) { - String message = "## Command info for /" + command.getCommandName(); - if (command.getDescription() != null) { - message += "\n- Description: " + command.getDescription(); - } + public static String buildCommandHelpMessage(CommandContainer container) { - message += "\n\n-# *: required argument"; + String message = "## Command info for /" + container.getCommand().getCommandName(); + if (container.getCommand().getDescription() != null) { + message += "\n- Description: " + container.getCommand().getDescription(); + } + message += "\n" + container.getCommandOptions().printUsage(); return message; } @@ -280,7 +281,7 @@ public class MessageFunction { if (exception.getCause() != null) { String cause; if (exception.getCause().getMessage() != null && !exception.getCause().getMessage().isEmpty()) { - cause = exception.getCause().getMessage().substring(0, 64); + cause = exception.getCause().getMessage().substring(0, Math.min(exception.getCause().getMessage().length(), 64)); } else { cause = exception.getCause().getClass().getName(); } @@ -316,19 +317,4 @@ public class MessageFunction { return editEmbed.build(); } - public static void testMessageAgainstBlacklist(Message message, MessageChannel channel, Set cache) { - boolean found = false; - for (String word : cache) { - if (message.getContentRaw().toLowerCase().contains(word)) { - found = true; - break; - } - } - - if (found) { - message.delete().queue(); - channel.sendMessage("Not in my christian server").queue(); - } - } - } diff --git a/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java index 453063d..e6ca08c 100644 --- a/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java +++ b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java @@ -6,6 +6,7 @@ package me.bobert.netneutrality.utils; */ import me.bobert.netneutrality.context.MusicChannelContext; +import me.bobert.netneutrality.context.SlashCommandContext; import me.bobert.netneutrality.core.context.Context; import me.bobert.netneutrality.exceptions.ContextException; import me.bobert.netneutrality.music.MusicManager; @@ -87,6 +88,14 @@ public class MusicUtils { return memberVoiceState.getChannel(); } + public static boolean isUserDj(Context context) { + if (context instanceof SlashCommandContext) { + return true; + } + + return isUserDj(context.getGuildUser()); + } + public static boolean isUserDj(Member member) { MusicManager manager = MusicManager.get(member.getGuild()); Member dj = manager.getScheduler().getDj(); diff --git a/src/main/java/me/bobert/netneutrality/utils/UserFunction.java b/src/main/java/me/bobert/netneutrality/utils/UserFunction.java index f3ef127..b733d32 100644 --- a/src/main/java/me/bobert/netneutrality/utils/UserFunction.java +++ b/src/main/java/me/bobert/netneutrality/utils/UserFunction.java @@ -6,12 +6,22 @@ package me.bobert.netneutrality.utils; * https://discord.gg/qUa6zNFZsd */ +import me.bobert.netneutrality.context.SlashCommandContext; +import me.bobert.netneutrality.core.context.Context; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; public class UserFunction { - + + public static boolean isAdmin(Context context) { + if (context instanceof SlashCommandContext) { + return true; + } + + return isAdmin(context.getGuildUser()); + } + public static boolean isAdmin(Member user) { boolean isAdmin = false; for(Role role : user.getRoles()) {