test
This commit is contained in:
140
src/main/java/cc/fascinated/bat/command/BatCommand.java
Normal file
140
src/main/java/cc/fascinated/bat/command/BatCommand.java
Normal file
@ -0,0 +1,140 @@
|
||||
package cc.fascinated.bat.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
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.SubcommandGroupData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Setter@Getter
|
||||
public abstract class BatCommand extends ListenerAdapter implements IBatCommand {
|
||||
/**
|
||||
* The snowflake of this command, if it
|
||||
* was successfully registered on Discord.
|
||||
*/
|
||||
private Long snowflake;
|
||||
|
||||
/**
|
||||
* The data for this command.
|
||||
*/
|
||||
@NonNull private final SlashCommandData commandData;
|
||||
|
||||
/**
|
||||
* The category of this command.
|
||||
*/
|
||||
@NonNull private final CommandCategory category;
|
||||
|
||||
/**
|
||||
* The sub-commands for this command.
|
||||
*/
|
||||
@NonNull private final List<BatSubCommand> subCommands = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public BatCommand(@NonNull String name, @NonNull String description, @NonNull CommandCategory category, @NonNull OptionData... options) {
|
||||
this(name, description, category, DefaultMemberPermissions.ENABLED, options);
|
||||
}
|
||||
|
||||
public BatCommand(@NonNull String name, @NonNull String description, @NonNull CommandCategory category, @NonNull DefaultMemberPermissions defaultPermissions, @NonNull OptionData... options) {
|
||||
commandData = Commands.slash(name, description)
|
||||
.setDefaultPermissions(defaultPermissions)
|
||||
.addOptions(options);
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this command.
|
||||
*
|
||||
* @return the command name
|
||||
*/
|
||||
@Override @NonNull
|
||||
public String getName() {
|
||||
return commandData.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full name of this command.
|
||||
*
|
||||
* @return the full name
|
||||
*/
|
||||
@Override @NonNull
|
||||
public String getFullName() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of this command.
|
||||
*
|
||||
* @return the command description
|
||||
*/
|
||||
@Override @NonNull
|
||||
public String getDescription() {
|
||||
return commandData.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mentionable name of this command.
|
||||
*
|
||||
* @return the mentionable name
|
||||
*/
|
||||
@Override @NonNull
|
||||
public final String getMentionable() {
|
||||
return "</%s:%s>".formatted(getName(), getSnowflake());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a sub-command to this command.
|
||||
*
|
||||
* @param subCommand the sub-command to register
|
||||
*/
|
||||
protected final void withSubCommand(@NonNull BatSubCommand subCommand) {
|
||||
subCommands.add(subCommand);
|
||||
|
||||
// Add the sub-command to the command data
|
||||
if (subCommand.getGroupName() == null) { // No command group
|
||||
getCommandData().addSubcommands(subCommand.getCommandData());
|
||||
} else {
|
||||
// Add to any existing command group, otherwise create a new one
|
||||
for (SubcommandGroupData subCommandGroup : getCommandData().getSubcommandGroups()) {
|
||||
if (subCommand.getGroupName().equals(subCommandGroup.getName())) {
|
||||
subCommandGroup.addSubcommands(subCommand.getCommandData());
|
||||
return;
|
||||
}
|
||||
}
|
||||
getCommandData().addSubcommandGroups(new SubcommandGroupData(
|
||||
subCommand.getGroupName(),
|
||||
"The " + subCommand.getGroupName() + " group for /" + getName() + " " + subCommand.getName()
|
||||
).addSubcommands(subCommand.getCommandData()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sub-command by name.
|
||||
*
|
||||
* @param group the sub-command group, null if none
|
||||
* @param name the sub-command name
|
||||
* @return the sub-command, null if not found
|
||||
*/
|
||||
public final BatSubCommand getSubCommand(String group, @NonNull String name) {
|
||||
for (BatSubCommand subCommand : subCommands) {
|
||||
String commandGroup = subCommand.getGroupName();
|
||||
|
||||
// Check if groups match (both null or both equal)
|
||||
boolean groupsMatch = (group == null && commandGroup == null) ||
|
||||
(commandGroup != null && commandGroup.equals(group));
|
||||
if (subCommand.getName().equals(name) && groupsMatch) {
|
||||
return subCommand;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
82
src/main/java/cc/fascinated/bat/command/BatSubCommand.java
Normal file
82
src/main/java/cc/fascinated/bat/command/BatSubCommand.java
Normal file
@ -0,0 +1,82 @@
|
||||
package cc.fascinated.bat.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
|
||||
/**
|
||||
* A sub-command for a {@link BatCommand}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Setter @Getter
|
||||
public abstract class BatSubCommand implements IBatCommand {
|
||||
/**
|
||||
* The parent of this sub-command.
|
||||
*/
|
||||
@NonNull private final BatCommand parent;
|
||||
|
||||
/**
|
||||
* The snowflake of this sub=command, if it
|
||||
* was successfully registered on Discord.
|
||||
*/
|
||||
private Long snowflake;
|
||||
|
||||
/**
|
||||
* The full name of this command, including the
|
||||
* parent name, category, and sub-command name.
|
||||
*/
|
||||
private String fullName;
|
||||
|
||||
/**
|
||||
* The optional group name for this sub-command.
|
||||
*/
|
||||
private final String groupName;
|
||||
|
||||
/**
|
||||
* The data for this sub-command.
|
||||
*/
|
||||
@NonNull private final SubcommandData commandData;
|
||||
|
||||
public BatSubCommand(@NonNull BatCommand parent, @NonNull String name, @NonNull String description, @NonNull OptionData... options) {
|
||||
this(parent, null, name, description, options);
|
||||
}
|
||||
|
||||
public BatSubCommand(@NonNull BatCommand parent, String groupName, @NonNull String name, @NonNull String description, @NonNull OptionData... options) {
|
||||
this.parent = parent;
|
||||
this.groupName = groupName;
|
||||
commandData = new SubcommandData(name, description).addOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this command.
|
||||
*
|
||||
* @return the command name
|
||||
*/
|
||||
@Override @NonNull
|
||||
public String getName() {
|
||||
return commandData.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of this command.
|
||||
*
|
||||
* @return the command description
|
||||
*/
|
||||
@Override @NonNull
|
||||
public String getDescription() {
|
||||
return commandData.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mentionable name of this command.
|
||||
*
|
||||
* @return the mentionable name
|
||||
*/
|
||||
@Override @NonNull
|
||||
public final String getMentionable() {
|
||||
return "</%s:%s>".formatted(getFullName(), getSnowflake());
|
||||
}
|
||||
}
|
25
src/main/java/cc/fascinated/bat/command/CommandCategory.java
Normal file
25
src/main/java/cc/fascinated/bat/command/CommandCategory.java
Normal file
@ -0,0 +1,25 @@
|
||||
package cc.fascinated.bat.command;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
|
||||
/**
|
||||
* The categories of {@link BatCommand}'s.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Getter
|
||||
public enum CommandCategory {
|
||||
GENERAL("⚙️"),
|
||||
GUILD("🏠");
|
||||
|
||||
/**
|
||||
* The emoji for this category.
|
||||
*/
|
||||
@NonNull private final Emoji emoji;
|
||||
|
||||
CommandCategory(@NonNull String unicode) {
|
||||
emoji = Emoji.fromUnicode(unicode);
|
||||
}
|
||||
}
|
97
src/main/java/cc/fascinated/bat/command/IBatCommand.java
Normal file
97
src/main/java/cc/fascinated/bat/command/IBatCommand.java
Normal file
@ -0,0 +1,97 @@
|
||||
package cc.fascinated.bat.command;
|
||||
|
||||
import cc.fascinated.bat.common.EnumUtils;
|
||||
import cc.fascinated.bat.common.InteractionResponder;
|
||||
import cc.fascinated.bat.common.model.BatGuild;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.Command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A base Rufus command.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface IBatCommand {
|
||||
/**
|
||||
* Get the name of this command.
|
||||
*
|
||||
* @return the command name
|
||||
*/
|
||||
@NonNull String getName();
|
||||
|
||||
/**
|
||||
* Get the full name of this command.
|
||||
*
|
||||
* @return the full name
|
||||
*/
|
||||
@NonNull String getFullName();
|
||||
|
||||
/**
|
||||
* Get the description of this command.
|
||||
*
|
||||
* @return the command description
|
||||
*/
|
||||
@NonNull String getDescription();
|
||||
|
||||
/**
|
||||
* Get the mentionable name of this command.
|
||||
*
|
||||
* @return the mentionable name
|
||||
*/
|
||||
@NonNull String getMentionable();
|
||||
|
||||
/**
|
||||
* Fired when this command is executed.
|
||||
* <p>
|
||||
* If this command has {@link BatSubCommand}'s, this
|
||||
* will not be invoked for the parent, and instead only
|
||||
* for the sub-commands.
|
||||
* </p>
|
||||
*
|
||||
* @param guild the ticket guild that executed this command
|
||||
* @param discordGuild the Discord guild that executed this command
|
||||
* @param member the member that executed this command
|
||||
* @param responder the responder to the interaction
|
||||
* @param event the event that triggered this command
|
||||
*/
|
||||
default void execute(@NonNull BatGuild guild, @NonNull Guild discordGuild, @NonNull Member member, @NonNull InteractionResponder responder, @NonNull SlashCommandInteractionEvent event) {
|
||||
event.deferReply().queue(); // Thinking by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto complete this command.
|
||||
*
|
||||
* @param guild the ticket guild that executed this command
|
||||
* @param discordGuild the Discord guild that executed this command
|
||||
* @param member the member that executed this command
|
||||
* @param event the event that triggered this auto completion
|
||||
* @return the choices to auto complete
|
||||
*/
|
||||
@NonNull
|
||||
default List<Command.Choice> autoComplete(@NonNull BatGuild guild, @NonNull Guild discordGuild, @NonNull Member member, @NonNull CommandAutoCompleteInteractionEvent event) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the choices for an enum.
|
||||
*
|
||||
* @param enumClass the enum class to get the choices for
|
||||
* @return the enum choices
|
||||
* @param <T> the enum type
|
||||
*/
|
||||
@NonNull
|
||||
default <T extends Enum<T>> List<Command.Choice> getEnumChoices(@NonNull Class<T> enumClass) {
|
||||
return Arrays.stream(enumClass.getEnumConstants())
|
||||
.map(constant -> new Command.Choice(EnumUtils.getEnumName(constant), constant.name()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package cc.fascinated.bat.command.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Commands annotated with this will only
|
||||
* be executable by the bot owner.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface BotOwner { }
|
@ -0,0 +1,16 @@
|
||||
package cc.fascinated.bat.command.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Commands annotated with this will send
|
||||
* "thinking..." message when executed.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Thinking {}
|
186
src/main/java/cc/fascinated/bat/command/command/HelpCommand.java
Normal file
186
src/main/java/cc/fascinated/bat/command/command/HelpCommand.java
Normal file
@ -0,0 +1,186 @@
|
||||
package cc.fascinated.bat.command.command;
|
||||
|
||||
import cc.fascinated.bat.common.BatEmoji;
|
||||
import cc.fascinated.bat.common.Colors;
|
||||
import cc.fascinated.bat.common.InteractionResponder;
|
||||
import cc.fascinated.bat.common.MiscUtils;
|
||||
import cc.fascinated.bat.common.model.BatGuild;
|
||||
import cc.fascinated.bat.service.CommandService;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import lombok.NonNull;
|
||||
import cc.fascinated.bat.command.CommandCategory;
|
||||
import cc.fascinated.bat.command.IBatCommand;
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.BatSubCommand;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
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.emoji.Emoji;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.components.ActionRow;
|
||||
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
public final class HelpCommand extends BatCommand {
|
||||
private static final String CATEGORY_DROPDOWN_ID = "select-category";
|
||||
private static final String MAIN_MENU_BUTTON_ID = "main-help-menu";
|
||||
|
||||
/**
|
||||
* The command service to use for the registry.
|
||||
*/
|
||||
@NonNull private final CommandService commandService;
|
||||
|
||||
/**
|
||||
* The build properties for this app, null if not available.
|
||||
*/
|
||||
private final BuildProperties buildProperties;
|
||||
|
||||
public HelpCommand(@NonNull CommandService commandService, BuildProperties buildProperties) {
|
||||
super("helpbob", "View the commands for Rufus", CommandCategory.GENERAL);
|
||||
this.commandService = commandService;
|
||||
this.buildProperties = buildProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when this command is executed.
|
||||
* <p>
|
||||
* If this command has {@link BatSubCommand}'s, this
|
||||
* will not be invoked for the parent, and instead only
|
||||
* for the sub-commands.
|
||||
* </p>
|
||||
*
|
||||
* @param guild the ticket guild that executed this command
|
||||
* @param discordGuild the Discord guild that executed this command
|
||||
* @param member the member that executed this command
|
||||
* @param responder the responder to the interaction
|
||||
* @param event the event that triggered this command
|
||||
*/
|
||||
@Override
|
||||
public void execute(@NonNull BatGuild guild, @NonNull Guild discordGuild, @NonNull Member member, @NonNull InteractionResponder responder, @NonNull SlashCommandInteractionEvent event) {
|
||||
responder.reply(true, buildMainMenu(member, discordGuild), buildMainMenuComponents(discordGuild).toArray(new LayoutComponent[0])).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStringSelectInteraction(@NotNull StringSelectInteractionEvent event) {
|
||||
// Switch to the selected category
|
||||
if (event.getComponentId().equals(CATEGORY_DROPDOWN_ID)) {
|
||||
event.editMessageEmbeds(buildCategoryEmbed(Objects.requireNonNull(event.getMember()), CommandCategory.valueOf(event.getSelectedOptions().getFirst().getValue())))
|
||||
.setComponents(
|
||||
ActionRow.of(buildCategoryMenu()),
|
||||
ActionRow.of(Button.primary(MAIN_MENU_BUTTON_ID, "Main Menu").withEmoji(Emoji.fromUnicode("🏠")))
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
|
||||
// Switch to the main menu
|
||||
if (event.getComponentId().equals(MAIN_MENU_BUTTON_ID)) {
|
||||
event.editMessageEmbeds(buildMainMenu(Objects.requireNonNull(event.getMember()),
|
||||
Objects.requireNonNull(event.getGuild()))).setComponents(buildMainMenuComponents(event.getGuild())).queue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the embed for the main menu.
|
||||
*
|
||||
* @param member the member to build for
|
||||
* @param discordGuild the discord guild
|
||||
* @return the main menu
|
||||
*/
|
||||
@NonNull
|
||||
private MessageEmbed buildMainMenu(@NonNull Member member, @NonNull Guild discordGuild) {
|
||||
String categoryNames = Arrays.stream(CommandCategory.values())
|
||||
.map(category -> "- **%s Commands**: `%s`".formatted(MiscUtils.capitalize(category.name()), commandService.getCommands(member, category).size()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
// Send the help menu
|
||||
return new EmbedBuilder()
|
||||
.setColor(Colors.DEFAULT)
|
||||
.setTitle(BatEmoji.RUFUS + " Rufus Help Menu")
|
||||
.setDescription(MiscUtils.arrayToString(
|
||||
"Rufus is a smart and efficient Discord bot that streamlines ticket management for your server. With a focus on simplicity and responsiveness, Rufus helps your staff team handle support requests, bug reports, and general inquiries with ease.",
|
||||
"",
|
||||
"Here are my commands:",
|
||||
categoryNames,
|
||||
"",
|
||||
"To get a list of commands for an individual category, select it from the dropdown below. Interested and wanna use me in your server? [Invite Me](" + MiscUtils.getInviteUrl(discordGuild.getJDA()) + ")!"
|
||||
)).setFooter("Rufus v%s".formatted(buildProperties == null ? "0" : buildProperties.getVersion())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the components for the main menu.
|
||||
*
|
||||
* @param guild the guild to use for the JDA instance
|
||||
* @return the components
|
||||
*/
|
||||
@NonNull
|
||||
private List<LayoutComponent> buildMainMenuComponents(@NonNull Guild guild) {
|
||||
return Arrays.asList(
|
||||
ActionRow.of(buildCategoryMenu()),
|
||||
ActionRow.of(
|
||||
Button.link("https://rufus.rainnny.club", "Support Server").withEmoji(Emoji.fromUnicode("🏠")),
|
||||
Button.link(MiscUtils.getInviteUrl(guild.getJDA()), "Invite Me").withEmoji(Emoji.fromUnicode("🤖"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the dropdown menu for categories.
|
||||
*
|
||||
* @return the category dropdown menu
|
||||
*/
|
||||
@NonNull
|
||||
private StringSelectMenu buildCategoryMenu() {
|
||||
StringSelectMenu.Builder categoryDropdown = StringSelectMenu.create(CATEGORY_DROPDOWN_ID).setPlaceholder("👉🏼 Select a category...");
|
||||
for (CommandCategory category : CommandCategory.values()) {
|
||||
categoryDropdown.addOption(MiscUtils.capitalize(category.name()) + " Commands", category.name(), category.getEmoji());
|
||||
}
|
||||
return categoryDropdown.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the embed for the category menu.
|
||||
*
|
||||
* @param member the member to build for
|
||||
* @param category the category to build for
|
||||
* @return the category menu
|
||||
*/
|
||||
@NonNull
|
||||
private MessageEmbed buildCategoryEmbed(@NonNull Member member, @NonNull CommandCategory category) {
|
||||
// Build the command list
|
||||
StringBuilder commands = new StringBuilder();
|
||||
for (IBatCommand command : commandService.getCommands(member, category)) {
|
||||
// Skip parents that have sub-commands
|
||||
if (command instanceof BatCommand parentCommand && !parentCommand.getSubCommands().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
commands.append("- ").append(command.getMentionable()).append(" - ").append(command.getDescription()).append("\n");
|
||||
}
|
||||
|
||||
// Build the embed
|
||||
return new EmbedBuilder()
|
||||
.setColor(Colors.DEFAULT)
|
||||
.setTitle(category.getEmoji().getFormatted() + " " + MiscUtils.capitalize(category.name()) + " Commands")
|
||||
.setDescription(MiscUtils.arrayToString(
|
||||
"Here are the commands for the " + MiscUtils.capitalize(category.name()) + " category:",
|
||||
"",
|
||||
commands.toString()
|
||||
)).setFooter("Rufus v%s".formatted(buildProperties == null ? "0" : buildProperties.getVersion())).build();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user