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()) {