Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

236 changed files with 2161 additions and 8322 deletions

@ -16,4 +16,4 @@
# under the License. # under the License.
wrapperVersion=3.3.2 wrapperVersion=3.3.2
distributionType=only-script distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip

@ -1,5 +1,5 @@
# Stage 1: Build the application # Stage 1: Build the application
FROM maven:3.9.9-eclipse-temurin-17-alpine AS builder FROM maven:3.9.6-eclipse-temurin-17-alpine AS builder
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# Stage 2: Create the final lightweight image # Stage 2: Create the final lightweight image
FROM eclipse-temurin:17.0.13_11-jre-focal FROM eclipse-temurin:17.0.11_9-jre-focal
# Set the working directory # Set the working directory
WORKDIR /home/container WORKDIR /home/container

64
pom.xml

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version> <version>3.3.1</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
@ -41,7 +41,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version> <version>3.5.3</version>
<configuration> <configuration>
<createDependencyReducedPom>false</createDependencyReducedPom> <createDependencyReducedPom>false</createDependencyReducedPom>
</configuration> </configuration>
@ -70,17 +70,6 @@
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>fascinated-repo-public</id>
<name>Fascinated's Repository</name>
<url>https://repo.fascinated.cc/public</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Spring --> <!-- Spring -->
@ -99,26 +88,24 @@
<dependency> <dependency>
<groupId>io.sentry</groupId> <groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId> <artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.16.0</version> <version>7.10.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongock-bom</artifactId> <artifactId>mongock-bom</artifactId>
<version>5.5.0</version> <version>5.2.4</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongock-springboot-v3</artifactId> <artifactId>mongock-springboot-v3</artifactId>
<version>5.5.0</version> <version>5.2.4</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.mongock</groupId> <groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId> <artifactId>mongodb-springdata-v4-driver</artifactId>
<version>5.5.0</version> <version>5.2.4</version>
<scope>compile</scope>
</dependency> </dependency>
<!-- Redis for caching --> <!-- Redis for caching -->
@ -142,53 +129,52 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.34</version> <version>1.18.32</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Dependencies -->
<dependency> <dependency>
<groupId>io.github.freya022</groupId> <groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId> <artifactId>JDA</artifactId>
<version>2ed819ad15</version> <version>5.0.0-beta.24</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.11.0</version> <version>2.10.1</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents.client5</groupId> <groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId> <artifactId>httpclient5</artifactId>
<version>5.4.1</version> <version>5.3.1</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.jodah</groupId> <groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId> <artifactId>expiringmap</artifactId>
<version>0.5.11</version> <version>0.5.11</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>se.michaelthelin.spotify</groupId> <groupId>se.michaelthelin.spotify</groupId>
<artifactId>spotify-web-api-java</artifactId> <artifactId>spotify-web-api-java</artifactId>
<version>8.4.1</version> <version>8.4.0</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>commons-text</artifactId> <artifactId>caffeine</artifactId>
<version>1.12.0</version> <version>3.1.8</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>xyz.mcutils</groupId> <groupId>uk.co.conoregan</groupId>
<artifactId>mcutils-java-library</artifactId> <artifactId>themoviedbapi</artifactId>
<version>1.2.4</version> <version>2.1.1</version>
</dependency> </dependency>
<!-- Test Dependencies -->
<dependency> <dependency>
<groupId>com.github.Steppschuh</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>Java-Markdown-Generator</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<version>1.3.2</version> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

@ -1,3 +1,6 @@
{ {
"extends": ["config:recommended", ":dependencyDashboard"] "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>Fascinated/renovate-config"
]
} }

@ -6,12 +6,12 @@ import cc.fascinated.bat.service.EventService;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import io.mongock.runner.springboot.EnableMongock; import io.mongock.runner.springboot.EnableMongock;
import jakarta.annotation.PreDestroy;
import lombok.NonNull; import lombok.NonNull;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import java.io.File; import java.io.File;
@ -20,7 +20,7 @@ import java.nio.file.StandardCopyOption;
import java.util.Objects; import java.util.Objects;
@EnableScheduling @EnableScheduling
@SpringBootApplication @SpringBootApplication(scanBasePackages = "cc.fascinated.bat")
@EnableMongock @EnableMongock
@Log4j2(topic = "Bat") @Log4j2(topic = "Bat")
public class BatApplication { public class BatApplication {
@ -39,20 +39,16 @@ public class BatApplication {
} }
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
// Start the application // Start the app
SpringApplication app = new SpringApplication(BatApplication.class); SpringApplication.run(BatApplication.class, args);
app.setRegisterShutdownHook(false); // Disable the default shutdown hook
ConfigurableApplicationContext context = app.run(args);
// Register the shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("Shutting down...");
for (EventListener listener : EventService.LISTENERS) {
listener.onShutdown();
}
context.close();
}));
log.info("APP IS RUNNING IN %s MODE!!!!!!!!!".formatted(Config.isProduction() ? "PRODUCTION" : "DEVELOPMENT")); log.info("APP IS RUNNING IN %s MODE!!!!!!!!!".formatted(Config.isProduction() ? "PRODUCTION" : "DEVELOPMENT"));
} }
@PreDestroy
public void onShutdown() {
log.info("Shutting down...");
for (EventListener listener : EventService.LISTENERS) {
listener.onSpringShutdown();
}
}
} }

@ -4,7 +4,7 @@ package cc.fascinated.bat;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class Consts { public class Consts {
public static final String INVITE_URL = "https://dsc.gg/batbot"; public static final String INVITE_URL = "https://discord.com/oauth2/authorize?client_id=1254161119975833652&permissions=8&integration_type=0&scope=bot+applications.commands";
public static final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG"; public static final String SUPPORT_INVITE_URL = "https://discord.gg/invite/yjj2U3ctEG";
public static final String BOT_OWNER = "474221560031608833"; public static final String BOT_OWNER = "474221560031608833";
public static final String PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt"; public static final String PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt";

@ -8,7 +8,7 @@ import net.dv8tion.jda.api.entities.emoji.Emoji;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Log4j2(topic = "Emojis") @Log4j2
public class Emojis { public class Emojis {
public static final Emoji SPOTIFY_EMOJI; public static final Emoji SPOTIFY_EMOJI;
public static final Emoji CHECK_MARK_EMOJI; public static final Emoji CHECK_MARK_EMOJI;
@ -17,19 +17,29 @@ public class Emojis {
public static final Emoji PAUSE_EMOJI; public static final Emoji PAUSE_EMOJI;
public static final Emoji PLAY_EMOJI; public static final Emoji PLAY_EMOJI;
public static final Emoji SKIP_EMOJI; public static final Emoji SKIP_EMOJI;
public static final Emoji HOME_EMOJI;
/**
* Presence Status Emojis
*/
public static final Emoji ONLINE_EMOJI;
public static final Emoji IDLE_EMOJI;
public static final Emoji DND_EMOJI;
public static final Emoji OFFLINE_EMOJI;
static { static {
log.info("Loading emojis..."); log.info("Loading emojis...");
JDA jda = DiscordService.JDA; JDA jda = DiscordService.JDA;
SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479"); SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479");
CHECK_MARK_EMOJI = jda.getEmojiById("1258922852669853817"); CHECK_MARK_EMOJI = jda.getEmojiById("1256633734065557676");
CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897"); CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897");
SAD_FACE_EMOJI = jda.getEmojiById("1256636078258131055"); SAD_FACE_EMOJI = jda.getEmojiById("1256636078258131055");
ONLINE_EMOJI = jda.getEmojiById("1256662465668710430");
IDLE_EMOJI = jda.getEmojiById("1256662632203685991");
DND_EMOJI = jda.getEmojiById("1256662572933845032");
OFFLINE_EMOJI = jda.getEmojiById("1256662679402053662");
PAUSE_EMOJI = Emoji.fromUnicode(""); PAUSE_EMOJI = Emoji.fromUnicode("");
PLAY_EMOJI = Emoji.fromUnicode(""); PLAY_EMOJI = Emoji.fromUnicode("");
SKIP_EMOJI = Emoji.fromUnicode(""); SKIP_EMOJI = Emoji.fromUnicode("");
HOME_EMOJI = Emoji.fromUnicode("🏠");
log.info("Loaded emojis!"); log.info("Loaded emojis!");
} }
} }

@ -1,246 +1,97 @@
package cc.fascinated.bat.command; package cc.fascinated.bat.command;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.model.BatGuild; import lombok.Getter;
import cc.fascinated.bat.model.BatUser; import lombok.NonNull;
import lombok.*; import lombok.Setter;
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.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.internal.interactions.CommandDataImpl; import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import java.util.*; import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* @author Braydon * @author Fascinated (fascinated7)
*/ */
@Getter @Getter
public abstract class BatCommand { @Setter
public abstract class BatCommand implements BatCommandExecutor {
/** /**
* The info of this command. * The information about the command
*/ */
@NonNull private final CommandInfo commandInfo;
private final InternalCommandInfo info;
/** /**
* The feature this command belongs to. * The command data for the slash command
*/
private final CommandDataImpl commandData;
/**
* The sub commands of the command
*/
private final Map<String, BatSubCommand> subCommands = new HashMap<>();
/**
* The category of the command
*/
private Category category;
/**
* The feature that the command belongs to
*/ */
@Setter
private Feature feature; private Feature feature;
/** /**
* The snowflake of this command, set when * Whether the command can only be used by the bot owner
* this command is registered with Discord.
*/ */
@Setter private boolean botOwnerOnly;
private long snowflake;
/** /**
* The sub commands of this command, if any. * The command snowflake from Discord
*/ */
private final Map<String, BatCommand> subCommands = Collections.synchronizedMap(new HashMap<>()); private long commandSnowflake;
/**
* The internal data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private CommandDataImpl commandData;
/**
* The internal subcommand data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private SubcommandData subcommandData;
public BatCommand() { public BatCommand() {
if (!getClass().isAnnotationPresent(CommandInfo.class)) { this.commandInfo = getClass().getAnnotation(CommandInfo.class);
throw new IllegalStateException("Missing @CommandInfo annotation in " + getClass().getSimpleName()); this.category = this.commandInfo.category();
} this.botOwnerOnly = this.commandInfo.botOwnerOnly();
info = new InternalCommandInfo(getClass().getAnnotation(CommandInfo.class));
commandData = new CommandDataImpl(info.getName(), info.getDescription()) this.commandData = new CommandDataImpl(this.commandInfo.name(), this.commandInfo.description())
// .setDefaultPermissions(DefaultMemberPermissions.enabledFor(info.getPermissions())) .setGuildOnly(this.commandInfo.guildOnly());
.setIntegrationTypes(info.isUserInstall() ?
EnumSet.of(IntegrationType.USER_INSTALL) :
EnumSet.of(IntegrationType.GUILD_INSTALL)
).setContexts(info.isGuildOnly() ?
EnumSet.of(InteractionContextType.GUILD) :
EnumSet.of(InteractionContextType.GUILD, InteractionContextType.BOT_DM, InteractionContextType.PRIVATE_CHANNEL)
);
} }
/** /**
* Fired when this command is executed. * Adds a sub command to the command
* *
* @param guild the guild the command was executed in, if any * @param subCommand The sub command
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage the message that invoked this command, if any
* @param arguments the arguments of the command, if any
* @param event the event that invoked this command, if any
*/ */
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, public void addSubCommand(@NonNull BatSubCommand subCommand) {
Message commandMessage, String[] arguments, SlashCommandInteraction event) { this.subCommands.put(subCommand.getCommandInfo().name().toLowerCase(), subCommand);
this.commandData.addSubcommands(subCommand.getCommandData());
} }
/** /**
* Register the given sub commands. * Adds an option to the sub command
* *
* @param commands the commands to register * @param optionType the type of the option
* @param name the name of the option
* @param description the description of the option
* @param required whether the option is required
*/ */
protected final void addSubCommands(@NonNull BatCommand... commands) { protected void addOption(OptionType optionType, String name, String description, boolean required) {
for (BatCommand command : commands) { this.commandData.addOption(optionType, name, description, required);
// Copy info from the parent command
if (command.getInfo().getCategory() != info.getCategory()) {
command.getInfo().setCategory(info.getCategory());
}
if (command.getInfo().getPermissions().length == 0) {
command.getInfo().setPermissions(info.getPermissions());
}
if (command.getInfo().isGuildOnly() != info.isGuildOnly()) {
command.getInfo().setGuildOnly(info.isGuildOnly());
}
if (command.getInfo().isBotOwnerOnly() != info.isBotOwnerOnly()) {
command.getInfo().setBotOwnerOnly(info.isBotOwnerOnly());
}
command.setSubcommandData(new SubcommandData(command.getInfo().getName(), command.getInfo().getDescription()));
for (OptionData option : command.getCommandData().getOptions()) {
command.getSubcommandData().addOptions(option);
}
commandData.addSubcommands(command.getSubcommandData());
subCommands.put(command.getInfo().getName(), command);
}
} }
/** /**
* Add the given options * Gets all the options for the command
* to this command.
* *
* @param options the options to add * @param interaction The slash command interaction
* @return The option strings
*/ */
protected final void addOptions(OptionData... options) { public List<String> getOptions(SlashCommandInteraction interaction) {
commandData.addOptions(options); return interaction.getOptions().stream().map(OptionMapping::getName).toList();
}
/**
* Get the sub command by its class.
*
* @param clazz the class of the sub command
* @return the sub command
*/
public BatCommand getSubCommand(Class<? extends BatCommand> clazz) {
for (Map.Entry<String, BatCommand> entry : subCommands.entrySet()) {
if (entry.getValue().getClass().equals(clazz)) {
return entry.getValue();
}
}
return null;
}
/**
* Reply to the message or interaction with the given contents.
*
* @param message the message to reply to, null if interaction
* @param interaction the interaction to reply to, null if message
* @param contents the contents to reply with
*/
public void replyMessage(Message message, SlashCommandInteraction interaction, String contents) {
if (message != null) {
message.reply(contents).queue();
} else {
interaction.reply(contents).queue();
}
}
/**
* Reply to the message or interaction with the given embed.
*
* @param message the message to reply to, null if interaction
* @param interaction the interaction to reply to, null if message
* @param builder the embed builder to reply with
*/
public void replyEmbed(Message message, SlashCommandInteraction interaction, EmbedBuilder builder) {
if (message != null) {
message.replyEmbeds(builder.build()).queue();
} else {
interaction.replyEmbeds(builder.build()).queue();
}
}
/**
* Get the argument from the command.
*
* @param option the option to get from the slash command
* @param arguments the arguments of the invoked command
* @param argumentIndex the index of the argument in the command
* @param concatenateLeftover whether to concatenate the leftover arguments
* @param event the event that invoked the command
* @return the argument
*/
public Argument getArgument(String option, String[] arguments, int argumentIndex, boolean concatenateLeftover, SlashCommandInteraction event) {
return new Argument(option, arguments, argumentIndex, concatenateLeftover, event);
}
@AllArgsConstructor
public static class Argument {
/**
* The option to get from the slash command.
*/
private final String option;
/**
* The arguments of the invoked command.
*/
private final String[] arguments;
/**
* The index of the argument in the command.
*/
private final int argumentIndex;
/**
* Whether to concatenate the leftover arguments.
*/
private final boolean concatenateLeftover;
/**
* The event that invoked the command.
*/
private final SlashCommandInteraction event;
/**
* Get the argument from the command.
*
* @return the argument
*/
public String getAsString() {
if (event != null) { // Get the option from the event
OptionMapping option = event.getOption(this.option);
if (option == null) {
return null;
}
return option.getAsString();
}
if (arguments.length < argumentIndex) { // Check if the argument index is out of bounds
return null;
}
if (concatenateLeftover) { // Concatenate the leftover arguments
StringBuilder builder = new StringBuilder();
for (int i = argumentIndex; i < arguments.length; i++) {
builder.append(arguments[i]).append(" ");
}
return builder.toString().trim();
}
return arguments[argumentIndex]; // Get the argument at the index
}
} }
} }

@ -0,0 +1,31 @@
package cc.fascinated.bat.command;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
/**
* @author Fascinated (fascinated7)
*/
public interface BatCommandExecutor {
/**
* Executes the command using a slash command event.
*
* @param guild the bat guild the command was executed in (null if the command was executed in a DM)
* @param user the bat user that executed the command
* @param channel the channel the command was executed in
* @param member the member that executed the command
* @param event the slash command event
*/
default void execute(
BatGuild guild,
@NonNull BatUser user,
@NonNull MessageChannel channel,
Member member,
@NonNull SlashCommandInteraction event
) {
}
}

@ -0,0 +1,45 @@
package cc.fascinated.bat.command;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
public class BatSubCommand implements BatCommandExecutor {
/**
* The information about the sub command
*/
private final CommandInfo commandInfo;
/**
* The command data for the slash command
*/
private final SubcommandData commandData;
/**
* The commands snowflake from Discord
*/
private long commandSnowflake;
public BatSubCommand() {
this.commandInfo = getClass().getAnnotation(CommandInfo.class);
this.commandData = new SubcommandData(this.commandInfo.name(), this.commandInfo.description());
}
/**
* Adds an option to the sub command
*
* @param optionType the type of the option
* @param name the name of the option
* @param description the description of the option
* @param required whether the option is required
*/
public void addOption(OptionType optionType, String name, String description, boolean required) {
this.commandData.addOption(optionType, name, description, required);
}
}

@ -2,11 +2,9 @@ package cc.fascinated.bat.command;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.Emoji;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
@ -15,23 +13,32 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
@Getter @Getter
public enum Category { public enum Category {
GENERAL(Emoji.fromUnicode("U+2699"), "General"), GENERAL(Emoji.fromUnicode("U+2699"), "General", false),
FUN(Emoji.fromFormatted("U+1F973"), "Fun"), FUN(Emoji.fromFormatted("U+1F973"), "Fun", false),
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"), SERVER(Emoji.fromFormatted("U+1F5A5"), "Server", false),
MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"), MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation", false),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"), UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility", false),
MEDIA(Emoji.fromFormatted("U+1F3A5"), "Media"), MUSIC(Emoji.fromFormatted("U+1F3B5"), "Music", false),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber"); MOVIES_TV(Emoji.fromFormatted("U+1F3A5"), "Movies & TV", false),
MESSAGES(Emoji.fromFormatted("U+1F4A3"), "Snipe", false),
LOGS(Emoji.fromFormatted("U+1F4D1"), "Logs", false),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber", false),
BOT_ADMIN(null, null, true);
/** /**
* The emoji for the category * The emoji for the category
*/ */
@NonNull private final Emoji emoji; private final Emoji emoji;
/** /**
* The name of the category * The name of the category
*/ */
@NonNull private final String name; private final String name;
/**
* If the category is hidden
*/
private final boolean hidden;
/** /**
* Gets a category by its name * Gets a category by its name
@ -49,11 +56,11 @@ public enum Category {
} }
/** /**
* Gets the categories sorted by their name * Gets all the visible categories
* *
* @return the sorted categories * @return the visible categories
*/ */
public static List<Category> getSortedByName() { public static List<Category> getCategories() {
return Arrays.stream(Category.values()).sorted(Comparator.comparing(Category::getName)).toList(); return Arrays.stream(Category.values()).filter(category -> !category.isHidden()).toList();
} }
} }

@ -8,9 +8,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Annotated at the top of a class to define
* the default values for a {@link BatCommand}.
*
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -37,20 +34,6 @@ public @interface CommandInfo {
*/ */
boolean guildOnly() default true; boolean guildOnly() default true;
/**
* If the command can be user installed
*
* @return if the command is user installable
*/
boolean userInstall() default false;
/**
* If the command can be executed with a prefix
*
* @return if the command can be executed with a prefix
*/
boolean prefixAllowed() default false;
/** /**
* The required permissions for the command * The required permissions for the command
* *

@ -1,66 +0,0 @@
package cc.fascinated.bat.command;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import net.dv8tion.jda.api.Permission;
/**
* The internal command info of a {@link BatCommand}.
*
* @author Braydon
*/
@Setter(AccessLevel.PROTECTED) @Getter
public class InternalCommandInfo {
/**
* The name of the command.
*/
@NonNull private final String name;
/**
* The description of the command.
*/
@NonNull private final String description;
/**
* The category of the command.
*/
@NonNull private Category category;
/**
* The permissions required to run this command, if any.
*/
private Permission[] permissions;
/**
* Whether this command can only be ran within a guild.
*/
private boolean guildOnly;
/**
* Whether this command can be user installed.
*/
private final boolean userInstall;
/**
* Whether the command can only be ran by the bot owner.
*/
private boolean botOwnerOnly;
/**
* Whether the command can be ran with a prefix.
*/
private boolean prefixAllowed;
protected InternalCommandInfo(@NonNull CommandInfo annotation) {
name = annotation.name();
description = annotation.description();
category = annotation.category();
permissions = annotation.requiredPermissions();
guildOnly = annotation.guildOnly() && !annotation.userInstall();
userInstall = annotation.userInstall();
botOwnerOnly = annotation.botOwnerOnly();
prefixAllowed = annotation.prefixAllowed();
}
}

@ -1,48 +0,0 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "ChannelUtils")
public class ChannelUtils {
/**
* Gets the user with the given id
*
* @param id the id of the user
* @param retries the amount of retries
* @return the user with the given id
*/
private static TextChannel getTextChannel(String id, int retries) {
if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}
if (id == null) {
return null;
}
TextChannel channel = DiscordService.JDA.getTextChannelById(id);
if (channel == null) {
return getTextChannel(id, retries + 1);
}
if (retries >= 5) {
log.info("Found text channel \"{}\" after {} retries.", channel.getName(), retries);
}
return channel;
}
/**
* Gets the user with the given id
*
* @param id the id of the user
* @return the user with the given id
*/
public static TextChannel getTextChannel(String id) {
return getTextChannel(id, 0);
}
}

@ -3,18 +3,12 @@ package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.util.Date;
import java.util.Locale;
@UtilityClass @UtilityClass
public class DateUtils { public class DateUtils {
private static final ZoneId ZONE_ID = ZoneId.of("Europe/London");
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT;
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withLocale(Locale.UK)
.withZone(ZONE_ID);
/** /**
* Gets the date from a string. * Gets the date from a string.
@ -25,14 +19,4 @@ public class DateUtils {
public static Date getDateFromString(String date) { public static Date getDateFromString(String date) {
return Date.from(Instant.from(FORMATTER.parse(date))); return Date.from(Instant.from(FORMATTER.parse(date)));
} }
/**
* Formats a date to a string.
*
* @param date The date to format.
* @return The formatted date.
*/
public String formatDate(Date date) {
return SIMPLE_FORMATTER.format(date.toInstant());
}
} }

@ -5,33 +5,30 @@ import lombok.NonNull;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class DescriptionBuilder { public class EmbedDescriptionBuilder {
/** /**
* Where the description is stored * Where the description is stored
*/ */
private final StringBuilder builder = new StringBuilder(); private final StringBuilder builder = new StringBuilder();
public DescriptionBuilder(String title) { public EmbedDescriptionBuilder(String title) {
if (title == null) {
return;
}
builder.append("**").append(title).append("**").append("\n"); builder.append("**").append(title).append("**").append("\n");
} }
@NonNull @NonNull
public DescriptionBuilder appendLine(@NonNull String line, boolean arrow) { public EmbedDescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
builder.append(arrow ? "" : "").append(line).append("\n"); builder.append(arrow ? "" : "").append(line).append("\n");
return this; return this;
} }
@NonNull @NonNull
public DescriptionBuilder appendSubtitle(@NonNull String title) { public EmbedDescriptionBuilder appendSubtitle(@NonNull String title) {
builder.append("**").append(title).append("**").append("\n"); builder.append("**").append(title).append("**").append("\n");
return this; return this;
} }
@NonNull @NonNull
public DescriptionBuilder emptyLine() { public EmbedDescriptionBuilder emptyLine() {
builder.append("\n"); builder.append("\n");
return this; return this;
} }

@ -1,9 +1,7 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.config.Config;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -44,30 +42,4 @@ public class EmbedUtils {
.setTimestamp(LocalDateTime.now()) .setTimestamp(LocalDateTime.now())
.setColor(Colors.SUCCESS); .setColor(Colors.SUCCESS);
} }
/**
* Builds a generic interaction error embed
*
* @param ex the exceptionk
* @return the embed builder
*/
public static EmbedBuilder genericInteractionError(Exception ex) {
TextChannel channel = ChannelUtils.getTextChannel(Config.INSTANCE.getLogsChannel());
EmbedBuilder embed = errorEmbed()
.setDescription("""
An error has occurred while processing %s interaction. If this issue persists, please contact the developers.
Cause: `%s`
```java
%s
```""".formatted(
channel == null ? "an" : "your",
ex.getStackTrace()[0].getClassName(),
ex.getLocalizedMessage()
));
if (channel != null) {
channel.sendMessageEmbeds(embed.build()).queue();
}
return embed;
}
} }

@ -1,11 +1,8 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class EnumUtils { public class EnumUtils {
/** /**
* Gets the name of the enum * Gets the name of the enum

@ -1,58 +0,0 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.awt.*;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class HexColorUtils {
/**
* Checks if the given string is a hex color
*
* @param hexColor the hex color to check
* @return if the given string is a hex color
*/
public static boolean isHexColor(String hexColor) {
return hexColor.matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
}
/**
* Converts a hex color to a Color object
*
* @param hex the hex color to convert
* @return the Color object
*/
public static Color hexToColor(String hex) {
if (hex == null || (!hex.matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"))) {
throw new IllegalArgumentException("Invalid hex color code");
}
// Remove the '#' character
hex = hex.substring(1);
// If the hex code is 3 characters long, expand it to 6 characters
if (hex.length() == 3) {
char r = hex.charAt(0);
char g = hex.charAt(1);
char b = hex.charAt(2);
hex = "" + r + r + g + g + b + b;
}
// Convert the hex string to an integer and create a Color object
int rgb = Integer.parseInt(hex, 16);
return new Color(rgb);
}
/**
* Converts a Color object to a hex color
*
* @param color the Color object to convert
* @return the hex color
*/
public static String colorToHex(Color color) {
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
}
}

@ -1,118 +0,0 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.InteractionService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji;
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.ActionComponent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.utils.data.SerializableData;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static net.dv8tion.jda.api.interactions.components.Component.Type.STRING_SELECT;
/**
* @author Fascinated (fascinated7)
*/
@Getter
public class InteractionBuilder {
/**
* The button interactions
*/
private final Map<Button, Consumer<ButtonInteractionEvent>> buttonInteractions = new HashMap<>();
/**
* The select menu interactions
*/
private final Map<SelectOption, Consumer<StringSelectInteractionEvent>> selectMenuInteractions = new HashMap<>();
/**
* The select menu id
*/
private String selectMenuId;
public InteractionBuilder() {
InteractionService.INSTANCE.addInteractionBuilder(this);
}
/**
* Adds a button to the interaction builder
*
* @param display The display of the button
* @param onClick The consumer to run when the button is clicked
* @return - The interaction builder
*/
public InteractionBuilder addButton(String display, Consumer<ButtonInteractionEvent> onClick) {
String id = StringUtils.randomString(8);
this.buttonInteractions.put(Button.primary(id, display), onClick);
return this;
}
/**
* Adds a URL button to the interaction builder
*
* @param display The display of the button
* @param url The url to open when the button is clicked
* @return - The interaction builder
*/
public InteractionBuilder addUrlButton(String display, String url, Emoji emoji) {
this.buttonInteractions.put(Button.link(url, display).withEmoji(emoji), event -> {});
return this;
}
/**
* Adds a string selection to the interaction builder
*
* @param display the name of the selection
* @param description the description of the selection
* @param emoji the emoji of the selection
* @param onClick the consumer to run when the selection is clicked
* @return the interaction builder
*/
public InteractionBuilder addStringSelect(String display, String description, Emoji emoji, Consumer<StringSelectInteractionEvent> onClick) {
String id = StringUtils.randomString(8);
this.selectMenuInteractions.put(SelectOption.of(display, id).withDescription(description).withEmoji(emoji), onClick);
return this;
}
/**
* Builds the interactions into an action row
*
* @return The action row
*/
public List<ActionRow> build() {
List<ActionRow> components = new ArrayList<>();
if (!this.getButtonInteractions().isEmpty()) {
List<Button> buttons = new ArrayList<>(this.getButtonInteractions().keySet());
components.add(ActionRow.of(buttons));
}
if (!this.getSelectMenuInteractions().isEmpty()) {
List<SelectOption> options = new ArrayList<>(this.getSelectMenuInteractions().keySet());
String id = StringUtils.randomString(8);
this.selectMenuId = id;
components.add(ActionRow.of(StringSelectMenu.create(id)
.addOptions(options)
.build()));
}
return components;
}
}

@ -1,21 +0,0 @@
package cc.fascinated.bat.common;
/**
* @author Fascinated (fascinated7)
*/
public class LongUtils {
/**
* Checks if a string is a long
*
* @param string the string to check
* @return if the string is a long
*/
public static boolean isLong(String string) {
try {
Long.parseLong(string);
return true;
} catch (NumberFormatException exception) {
return false;
}
}
}

@ -24,39 +24,4 @@ public final class MathUtils {
).format(number) ).format(number)
); );
} }
/**
* Clamps a value between a minimum and maximum.
*
* @param value The value to clamp.
* @param min The minimum value.
* @param max The maximum value.
* @return The clamped value.
*/
public static double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
/**
* Linearly interpolates between two values.
*
* @param a The first value.
* @param b The second value.
* @param t The interpolation value.
* @return The interpolated value.
*/
public static double lerp(double a, double b, double t) {
return a + t * (b - a);
}
/**
* Generates a random number between a minimum and maximum.
*
* @param min The minimum value.
* @param max The maximum value.
* @return The random value.
*/
public static double random(double min, double max) {
return Math.random() * (max - min) + min;
}
} }

@ -2,7 +2,6 @@ package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
@ -11,7 +10,6 @@ import java.util.Comparator;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class MemberUtils { public class MemberUtils {
/** /**
* Checks if a user has permission to edit another user * Checks if a user has permission to edit another user

@ -1,20 +1,17 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Locale; import java.util.Locale;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class NumberFormatter { public class NumberFormatter {
/** /**
* The suffixes for the numbers * The suffixes for the numbers
*/ */
private static final String[] SUFFIXES = new String[] { "K", "M", "B", "T", "Q", "QT", "S", "SP", "O", "N", "D", "UD", "DD", "TD" }; private static final String[] SUFFIXES = new String[] { "K", "M", "B", "T", "Q", "QT", "S", "SP", "O", "N", "D", "UD", "DD", "TD" };
private static final DecimalFormat FORMAT = new DecimalFormat("#,##0.##"); private static final DecimalFormat FORMAT = new DecimalFormat("###.##");
/** /**
* Format the provided double * Format the provided double
@ -47,8 +44,8 @@ public class NumberFormatter {
* @param input the value to format * @param input the value to format
* @return the formatted double, in the format of xx,xxx,xxx * @return the formatted double, in the format of xx,xxx,xxx
*/ */
public static String simpleFormat(double input) { public static String formatCommas(double input) {
return FORMAT.format(input); return String.format("%,.0f", input);
} }
/** /**

@ -1,20 +0,0 @@
package cc.fascinated.bat.common;
/**
* @author Fascinated (fascinated7)
*/
public class NumberUtils {
/**
* Parses a string to an integer
*
* @param input the string to parse
* @return the parsed integer, or -1 if invalid
*/
public static int parseInt(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException exception) {
return -1;
}
}
}

@ -3,7 +3,6 @@ package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication; import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.model.token.paste.PasteUploadToken; import cc.fascinated.bat.model.token.paste.PasteUploadToken;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import java.net.URI; import java.net.URI;
@ -14,8 +13,7 @@ import java.net.http.HttpResponse;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass @Log4j2
@Log4j2(topic = "PasteUtils")
public class PasteUtils { public class PasteUtils {
private static final String PASTE_URL = "https://paste.fascinated.cc/"; private static final String PASTE_URL = "https://paste.fascinated.cc/";
private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload"; private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload";

@ -1,18 +1,12 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import java.awt.*;
import java.util.EnumSet;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class RoleUtils { public class RoleUtils {
/** /**
* Checks if a member has permission to give the role to another member * Checks if a member has permission to give the role to another member
@ -25,60 +19,4 @@ public class RoleUtils {
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) { public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
return member.getRoles().stream().anyMatch(r -> r.getPosition() > role.getPosition()); return member.getRoles().stream().anyMatch(r -> r.getPosition() > role.getPosition());
} }
/**
* Checks if a member has a higher role than the specified role
*
* @param member the member to check
* @param targetMember the member to check against
* @return if the member has a higher role
*/
public static boolean hasHigherRole(Member member, Member targetMember) {
return member.getRoles().stream().anyMatch(r -> targetMember.getRoles().stream().anyMatch(tr -> tr.getPosition() < r.getPosition()));
}
/**
* Gets the formatted permissions of a role
*
* @param role the role to get the formatted permissions of
* @return the formatted permissions
*/
public String getFormattedPermissions(Role role) {
StringBuilder formattedPermissions = new StringBuilder();
EnumSet<Permission> permissions = role.getPermissions();
if (permissions.isEmpty()) {
return "`None`";
}
for (Permission permission : permissions) {
formattedPermissions.append("`").append(permission.getName()).append("`, ");
}
return formattedPermissions.substring(0, formattedPermissions.length() - 2);
}
/**
* Gets the formatted color of a role
*
* @param color the color string to get the formatted color of
* @return the formatted color
*/
public String getFormattedColor(Color color) {
if (color == null) {
return "Default";
}
String colorHex = HexColorUtils.colorToHex(color);
return "`%s` *[(view)](%s)*".formatted(
colorHex,
"https://www.colorhexa.com/%s".formatted(colorHex.substring(1))
);
}
/**
* Gets the formatted color of a role
*
* @param role the role to get the formatted icon of
* @return the formatted icon
*/
public String getFormattedColor(Role role) {
return getFormattedColor(role.getColor());
}
} }

@ -3,7 +3,6 @@ package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService; import cc.fascinated.bat.service.SpotifyService;
import lombok.NonNull; import lombok.NonNull;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying; import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import se.michaelthelin.spotify.model_objects.specification.Track; import se.michaelthelin.spotify.model_objects.specification.Track;
@ -11,8 +10,7 @@ import se.michaelthelin.spotify.model_objects.specification.Track;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass @Log4j2
@Log4j2(topic = "Spotify Utils")
public class SpotifyUtils { public class SpotifyUtils {
/** /**
* Gets the URL of the track that is currently playing. * Gets the URL of the track that is currently playing.

@ -1,11 +1,8 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass
public class StringUtils { public class StringUtils {
/** /**
* Generates a random string * Generates a random string
@ -37,18 +34,4 @@ public class StringUtils {
} }
return inputString; return inputString;
} }
/**
* Formats bytes into a human-readable format
*
* @param bytes the bytes
* @return the formatted bytes
*/
public static String formatBytes(long bytes) {
int unit = 1024;
if (bytes < unit) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
char pre = "KMGTPE".charAt(exp-1);
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
} }

@ -1,5 +1,6 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
/** /**
@ -17,7 +18,7 @@ public class TextChannelUtils {
if (id == null) { if (id == null) {
return false; return false;
} }
return ChannelUtils.getTextChannel(id) != null; return DiscordService.JDA.getTextChannelById(id) != null;
} }
/** /**

@ -1,45 +0,0 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.DiscordService;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.User;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "UserUtils")
public class UserUtils {
/**
* Gets the user with the given id
*
* @param id the id of the user
* @param retries the amount of retries
* @return the user with the given id
*/
private static User getUser(String id, int retries) {
if (retries >= 10) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}
User user = DiscordService.JDA.retrieveUserById(id).complete();
if (user == null) {
return getUser(id, retries + 1);
}
if (retries >= 5) {
log.info("Found user \"{}\" after {} retries.", user.getGlobalName(), retries);
}
return user;
}
/**
* Gets the user with the given id
*
* @param id the id of the user
* @return the user with the given id
*/
public static User getUser(String id) {
return getUser(id, 0);
}
}

@ -1,22 +0,0 @@
package cc.fascinated.bat.common.beatsaber.leaderboard;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor @Getter
public class Leaderboard {
/**
* The name of the leaderboard.
*/
private final String name;
/**
* The version of the leaderboard.
*/
private int curveVersion;
/**
* The curve of the leaderboard.
*/
private final LeaderboardCurvePoint[] curve;
}

@ -1,10 +0,0 @@
package cc.fascinated.bat.common.beatsaber.leaderboard;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter @AllArgsConstructor
public class LeaderboardCurvePoint {
private final double a;
private final double b;
}

@ -1,167 +0,0 @@
package cc.fascinated.bat.common.beatsaber.leaderboard.impl;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.beatsaber.leaderboard.Leaderboard;
import cc.fascinated.bat.common.beatsaber.leaderboard.LeaderboardCurvePoint;
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
import lombok.extern.log4j.Log4j2;
import java.util.ArrayList;
import java.util.List;
@Log4j2(topic = "ScoreSaber Leaderboard")
public class ScoreSaberLeaderboard extends Leaderboard {
public static final ScoreSaberLeaderboard INSTANCE = new ScoreSaberLeaderboard();
/**
* The base multiplier for stars.
*/
private final double starMultiplier = 42.11;
/**
* no idea, ngl
*/
private final double weightCoefficient = 0.965;
public ScoreSaberLeaderboard() {
super("ScoreSaber", 1, new LeaderboardCurvePoint[] {
new LeaderboardCurvePoint(1.0, 5.367394282890631),
new LeaderboardCurvePoint(0.9995, 5.019543595874787),
new LeaderboardCurvePoint(0.999, 4.715470646416203),
new LeaderboardCurvePoint(0.99825, 4.325027383589547),
new LeaderboardCurvePoint(0.9975, 3.996793606763322),
new LeaderboardCurvePoint(0.99625, 3.5526145337555373),
new LeaderboardCurvePoint(0.995, 3.2022017597337955),
new LeaderboardCurvePoint(0.99375, 2.9190155639254955),
new LeaderboardCurvePoint(0.9925, 2.685667856592722),
new LeaderboardCurvePoint(0.99125, 2.4902905794106913),
new LeaderboardCurvePoint(0.99, 2.324506282149922),
new LeaderboardCurvePoint(0.9875, 2.058947159052738),
new LeaderboardCurvePoint(0.985, 1.8563887693647105),
new LeaderboardCurvePoint(0.9825, 1.697536248647543),
new LeaderboardCurvePoint(0.98, 1.5702410055532239),
new LeaderboardCurvePoint(0.9775, 1.4664726399289512),
new LeaderboardCurvePoint(0.975, 1.3807102743105126),
new LeaderboardCurvePoint(0.9725, 1.3090333065057616),
new LeaderboardCurvePoint(0.97, 1.2485807759957321),
new LeaderboardCurvePoint(0.965, 1.1552120359501035),
new LeaderboardCurvePoint(0.96, 1.0871883573850478),
new LeaderboardCurvePoint(0.955, 1.0388633331418984),
new LeaderboardCurvePoint(0.95, 1.0),
new LeaderboardCurvePoint(0.94, 0.9417362980580238),
new LeaderboardCurvePoint(0.93, 0.9039994071865736),
new LeaderboardCurvePoint(0.92, 0.8728710341448851),
new LeaderboardCurvePoint(0.91, 0.8488375988124467),
new LeaderboardCurvePoint(0.9, 0.825756123560842),
new LeaderboardCurvePoint(0.875, 0.7816934560296046),
new LeaderboardCurvePoint(0.85, 0.7462290664143185),
new LeaderboardCurvePoint(0.825, 0.7150465663454271),
new LeaderboardCurvePoint(0.8, 0.6872268862950283),
new LeaderboardCurvePoint(0.75, 0.6451808210101443),
new LeaderboardCurvePoint(0.7, 0.6125565959114954),
new LeaderboardCurvePoint(0.65, 0.5866010012767576),
new LeaderboardCurvePoint(0.6, 0.18223233667439062),
new LeaderboardCurvePoint(0.0, 0.0)
});
}
/**
* Gets the modifier for the given accuracy.
*
* @param accuracy The accuracy.
* @return The modifier.
*/
public double getModifier(double accuracy) {
accuracy = MathUtils.clamp(accuracy, 0, 100) / 100;
LeaderboardCurvePoint prev = this.getCurve()[1];
for (LeaderboardCurvePoint point : this.getCurve()) {
if (point.getA() <= accuracy) {
double distance = (prev.getA() - accuracy) / (prev.getA() - point.getA());
return MathUtils.lerp(prev.getB(), point.getB(), distance);
}
prev = point;
}
return 0;
}
/**
* Gets the pp for the given accuracy and stars.
*
* @param accuracy The accuracy.
* @param stars The stars.
* @return The pp.
*/
public double getPP(double accuracy, double stars) {
double pp = stars * this.starMultiplier;
double modifier = this.getModifier(accuracy);
return modifier * pp;
}
/**
* Gets the total pp for the given scores.
*
* @param scores The scores.
* @return The total pp.
*/
private double getTotalPP(List<ScoreSaberScoreToken> scores, int startIdx) {
double totalPP = 0;
for (int i = 0; i < scores.size(); i++) {
totalPP += Math.pow(this.weightCoefficient, i + startIdx) * scores.get(i).getPp();
}
return totalPP;
}
/**
* Gets the pp at the given index for the given scores.
*
* @param bottomScores The scores.
* @param idx The index.
* @return The pp.
*/
private double getRawPPAtIdx(List<ScoreSaberScoreToken> bottomScores, int idx, double expected) {
double oldBottomPP = this.getTotalPP(bottomScores, idx);
double newBottomPP = this.getTotalPP(bottomScores, idx + 1);
return (expected + oldBottomPP - newBottomPP) / Math.pow(this.weightCoefficient, idx);
}
/**
* Gets the raw pp per global pp for the given scores.
*
* @param scores The scores.
* @param expectedPP The expected pp.
* @return The raw pp per global pp.
*/
public double getRawPerGlobalPP(List<ScoreSaberScoreToken> scores, double expectedPP) {
int left = 0;
int right = scores.size() - 1;
int boundaryIdx = -1;
// Sort by PP
scores.sort((a, b) -> Double.compare(b.getPp(), a.getPp()));
while (left <= right) {
int mid = (left + right) / 2;
double bottomPP = this.getTotalPP(scores.subList(mid, scores.size()), mid);
List<ScoreSaberScoreToken> bottomSlice = new ArrayList<>(scores.subList(mid, scores.size()));
bottomSlice.add(0, scores.get(mid));
double modifiedBottomPP = this.getTotalPP(bottomSlice, mid);
double diff = modifiedBottomPP - bottomPP;
if (diff > expectedPP) {
boundaryIdx = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
if (boundaryIdx == -1) {
return this.getRawPPAtIdx(scores, 0, expectedPP);
} else {
return this.getRawPPAtIdx(scores.subList(boundaryIdx + 1, scores.size()), boundaryIdx + 1, expectedPP);
}
}
}

@ -23,11 +23,6 @@ public class Config {
*/ */
private final String adminGuild; private final String adminGuild;
/**
* The ID of the logs channel
*/
private final String logsChannel;
static { static {
// Are we running on production? // Are we running on production?
String appEnv = System.getenv("APP_ENV"); String appEnv = System.getenv("APP_ENV");
@ -35,12 +30,8 @@ public class Config {
} }
@Autowired @Autowired
public Config( public Config(@Value("${bat.admin-guild}") String adminGuild) {
@Value("${bat.admin-guild}") String adminGuild,
@Value("${bat.logs-channel}") String logsChannel
) {
INSTANCE = this; INSTANCE = this;
this.adminGuild = adminGuild; this.adminGuild = adminGuild;
this.logsChannel = logsChannel;
} }
} }

@ -7,32 +7,18 @@ import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberLeaderboardT
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberPlayerScoreToken; import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken; import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberScoreToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.sticker.GuildSticker;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent; import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent; import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.update.*; import net.dv8tion.jda.api.events.channel.update.GenericChannelUpdateEvent;
import net.dv8tion.jda.api.events.emoji.EmojiAddedEvent;
import net.dv8tion.jda.api.events.emoji.EmojiRemovedEvent;
import net.dv8tion.jda.api.events.emoji.update.EmojiUpdateNameEvent;
import net.dv8tion.jda.api.events.guild.GuildBanEvent; import net.dv8tion.jda.api.events.guild.GuildBanEvent;
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent; import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
import net.dv8tion.jda.api.events.guild.invite.GuildInviteCreateEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleAddEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRoleRemoveEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateBoostTimeEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent;
import net.dv8tion.jda.api.events.guild.override.GenericPermissionOverrideEvent;
import net.dv8tion.jda.api.events.guild.update.*;
import net.dv8tion.jda.api.events.guild.voice.GenericGuildVoiceEvent; import net.dv8tion.jda.api.events.guild.voice.GenericGuildVoiceEvent;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
@ -40,241 +26,225 @@ import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionE
import net.dv8tion.jda.api.events.message.MessageDeleteEvent; import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.events.role.RoleCreateEvent;
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
import net.dv8tion.jda.api.events.role.update.*;
import net.dv8tion.jda.api.events.sticker.GuildStickerAddedEvent;
import net.dv8tion.jda.api.events.sticker.GuildStickerRemovedEvent;
import net.dv8tion.jda.api.events.sticker.update.GuildStickerUpdateNameEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateAvatarEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateNameEvent;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import java.time.OffsetDateTime;
import java.util.List; import java.util.List;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public interface EventListener { public interface EventListener {
/**
* Called when a ScoreSaber score is received
*
* @param score the score that was set
* @param leaderboard the leaderboard that the score was set on
* @param player the player that set the score
*/
default void onScoresaberScoreReceived(@NonNull ScoreSaberPlayerScoreToken score, @NonNull ScoreSaberLeaderboardToken leaderboard, default void onScoresaberScoreReceived(@NonNull ScoreSaberPlayerScoreToken score, @NonNull ScoreSaberLeaderboardToken leaderboard,
@NonNull ScoreSaberScoreToken.LeaderboardPlayerInfo player) { @NonNull ScoreSaberScoreToken.LeaderboardPlayerInfo player) {
} }
/**
* Called when a user joins a guild
*
* @param guild the guild the user joined
* @param user the user that joined the guild
*/
default void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) { default void onGuildMemberJoin(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberJoinEvent event) {
} }
/**
* Called when a user leaves a guild
*
* @param guild the guild the user left
* @param user the user that left the guild
*/
default void onGuildMemberLeave(@NonNull BatGuild guild, BatUser user, @NonNull GuildMemberRemoveEvent event) { default void onGuildMemberLeave(@NonNull BatGuild guild, BatUser user, @NonNull GuildMemberRemoveEvent event) {
} }
/**
* Called when a user types a message
*
* @param guild the guild that the message was sent in
* @param user the user that sent the message
*/
default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) { default void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
} }
/**
* Called when a user updates a message
*
* @param guild the guild that the message was updated in
* @param user the user that updated the message
*/
default void onGuildMessageEdit(@NonNull BatGuild guild, @NonNull BatUser user, DiscordMessage oldMessage, default void onGuildMessageEdit(@NonNull BatGuild guild, @NonNull BatUser user, DiscordMessage oldMessage,
@NonNull DiscordMessage newMessage, @NonNull MessageUpdateEvent event) { @NonNull DiscordMessage newMessage, @NonNull MessageUpdateEvent event) {
} }
/**
* Called when a user deletes a message
*
* @param guild the guild that the message was deleted in
* @param user the user that deleted the message
*/
default void onGuildMessageDelete(@NonNull BatGuild guild, BatUser user, DiscordMessage message, @NonNull MessageDeleteEvent event) { default void onGuildMessageDelete(@NonNull BatGuild guild, BatUser user, DiscordMessage message, @NonNull MessageDeleteEvent event) {
} }
/**
* Called when a user selects a string
*
* @param guild the guild that the string was selected in
* @param user the user that selected the string
*/
default void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) { default void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {
} }
/**
* Called when a user interacts with a button
*
* @param guild the guild that the button was interacted with in
* @param user the user that interacted with the button
*/
default void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) { default void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
} }
/**
* Called when a user interacts with a modal
*
* @param guild the guild that the modal was interacted with in
* @param user the user that interacted with the modal
*/
default void onModalInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ModalInteractionEvent event) { default void onModalInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ModalInteractionEvent event) {
} }
/**
* Called when a user updates their global name
*
* @param user the user that updated their global name
* @param oldName the old global name
* @param newName the new global name
*/
default void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) { default void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) {
} }
/**
* Called when a user updates their nickname in a guild
*
* @param guild the guild that the user updated their nickname in
* @param user the user that updated their nickname
* @param oldName the old nickname
* @param newName the new nickname
*/
default void onGuildMemberUpdateNickname(@NonNull BatGuild guild, @NonNull BatUser user, String oldName, String newName, @NonNull GuildMemberUpdateNicknameEvent event) { default void onGuildMemberUpdateNickname(@NonNull BatGuild guild, @NonNull BatUser user, String oldName, String newName, @NonNull GuildMemberUpdateNicknameEvent event) {
} }
/**
* Called when a user gets roles added to them
*
* @param guild the guild that the user added the role in
* @param user the user that added the role
* @param rolesAdded the roles that were added
*/
default void onGuildMemberRoleAdd(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleAddEvent event) { default void onGuildMemberRoleAdd(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleAddEvent event) {
} }
/**
* Called when a user gets roles removed from them
*
* @param guild the guild that the user removed the role in
* @param user the user that removed the role
* @param rolesAdded the roles that were removed
*/
default void onGuildMemberRoleRemove(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleRemoveEvent event) { default void onGuildMemberRoleRemove(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull List<Role> rolesAdded, @NonNull GuildMemberRoleRemoveEvent event) {
} }
/**
* Called when a channel is created
*
* @param guild the guild that the channel was created in
*/
default void onChannelCreate(@NonNull BatGuild guild, @NonNull ChannelCreateEvent event) { default void onChannelCreate(@NonNull BatGuild guild, @NonNull ChannelCreateEvent event) {
} }
/**
* Called when a channel is deleted
*
* @param guild the guild that the channel was deleted in
*/
default void onChannelDelete(@NonNull BatGuild guild, @NonNull ChannelDeleteEvent event) { default void onChannelDelete(@NonNull BatGuild guild, @NonNull ChannelDeleteEvent event) {
} }
/**
* Called when a user is banned from a guild
*
* @param guild the guild that the user was banned from
* @param user the user that was banned
*/
default void onGuildMemberBan(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildBanEvent event) { default void onGuildMemberBan(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildBanEvent event) {
} }
/**
* Called when a user is unbanned from a guild
*
* @param guild the guild that the user was unbanned from
* @param user the user that was unbanned
*/
default void onGuildMemberUnban(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildUnbanEvent event) { default void onGuildMemberUnban(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildUnbanEvent event) {
} }
/**
* Called when a user gets timed out in a guild (gets muted)
*
* @param guild the guild that the user timed out in
* @param user the user that timed out
*/
default void onGuildMemberTimeout(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberUpdateTimeOutEvent event) { default void onGuildMemberTimeout(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberUpdateTimeOutEvent event) {
} }
/**
* Called when a channels state is updated
*
* @param guild the guild that the channel was updated in
* @param event the event that was fired
*/
default void onGenericChannelUpdate(@NonNull BatGuild guild, @NonNull GenericChannelUpdateEvent<?> event) { default void onGenericChannelUpdate(@NonNull BatGuild guild, @NonNull GenericChannelUpdateEvent<?> event) {
} }
/**
* Called when a user updates their username
*
* @param user the user that updated their name
* @param oldName the old username
* @param newName the new username
*/
default void onUserUpdateName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateNameEvent event) { default void onUserUpdateName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateNameEvent event) {
} }
/**
* Called when a user updates their avatar
*
* @param user the user that updated their avatar
* @param oldAvatarUrl the old avatar url
* @param newAvatarUrl the new avatar url
*/
default void onUserUpdateAvatar(@NonNull BatUser user, String oldAvatarUrl, String newAvatarUrl, @NonNull UserUpdateAvatarEvent event) { default void onUserUpdateAvatar(@NonNull BatUser user, String oldAvatarUrl, String newAvatarUrl, @NonNull UserUpdateAvatarEvent event) {
} }
/**
* Called when a user joins or leaves a voice channel
*
* @param guild the guild that the user joined or left the voice channel in
* @param user the user that joined or left the voice channel
*/
default void onGuildVoiceUpdate(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GenericGuildVoiceEvent event) { default void onGuildVoiceUpdate(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GenericGuildVoiceEvent event) {
} }
default void onGuildInviteCreate(@NonNull BatGuild guild, @NonNull Invite invite, @NonNull GuildInviteCreateEvent event) { /**
} * Called when Spring is shutting down
*/
default void onGuildMemberBoostAdd(@NonNull BatGuild guild, @NonNull BatUser user, OffsetDateTime oldBoostTime, default void onSpringShutdown() {
@NonNull OffsetDateTime newBoostTime, @NonNull GuildMemberUpdateBoostTimeEvent event) {
}
default void onGuildMemberBoostRemove(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull OffsetDateTime oldBoostTime,
OffsetDateTime newBoostTime, @NonNull GuildMemberUpdateBoostTimeEvent event) {
}
default void onEmojiAdd(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull EmojiAddedEvent event) {
}
default void onEmojiRemove(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull EmojiRemovedEvent event) {
}
default void onEmojiRename(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull String oldName, @NonNull String newName, @NonNull EmojiUpdateNameEvent event) {
}
default void onChannelUpdateName(@NonNull BatGuild guild, @NonNull ChannelUpdateNameEvent event) {
}
default void onPermissionOverride(@NonNull BatGuild guild, @NonNull GenericPermissionOverrideEvent event) {
}
default void onRoleUpdatePermissions(@NonNull BatGuild guild, @NonNull RoleUpdatePermissionsEvent event) {
}
default void onRoleCreate(@NonNull BatGuild guild, @NonNull RoleCreateEvent event) {
}
default void onRoleDelete(@NonNull BatGuild guild, @NonNull RoleDeleteEvent event) {
}
default void onRoleUpdateName(@NonNull BatGuild guild, @NonNull RoleUpdateNameEvent event) {
}
default void onChannelUpdateTopic(@NonNull BatGuild guild, @NonNull ChannelUpdateTopicEvent event) {
}
default void onChannelUpdateBitrate(@NonNull BatGuild guild, @NonNull ChannelUpdateBitrateEvent event) {
}
default void onRoleUpdateColor(@NonNull BatGuild guild, @NonNull RoleUpdateColorEvent event) {
}
default void onRoleUpdateIcon(@NonNull BatGuild guild, @NonNull RoleUpdateIconEvent event) {
}
default void onChannelUpdateNSFW(@NonNull BatGuild guild, @NonNull ChannelUpdateNSFWEvent event) {
}
default void onChannelUpdateUserLimit(@NonNull BatGuild guild, @NonNull ChannelUpdateUserLimitEvent event) {
}
default void onChannelUpdateRegion(@NonNull BatGuild guild, @NonNull ChannelUpdateRegionEvent event) {
}
default void onChannelUpdateSlowmode(@NonNull BatGuild guild, @NonNull ChannelUpdateSlowmodeEvent event) {
}
default void onGuildUpdateName(@NonNull BatGuild guild, String oldName, String newName, @NonNull GuildUpdateNameEvent event) {
}
default void onGuildStickerAdd(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerAddedEvent event) {
}
default void onGuildStickerRemove(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerRemovedEvent event) {
}
default void onGuildStickerRename(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull String oldName,
@NonNull String newName, @NonNull GuildStickerUpdateNameEvent event) {
}
default void onGuildUpdateAfkChannel(@NonNull BatGuild guild, VoiceChannel oldChannel, VoiceChannel newChannel, @NonNull GuildUpdateAfkChannelEvent event) {
}
default void onGuildUpdateAfkTimeout(@NonNull BatGuild guild, Guild.Timeout oldTimeout, Guild.Timeout newTimeout, @NonNull GuildUpdateAfkTimeoutEvent event) {
}
default void onGuildUpdateBanner(@NonNull BatGuild guild, String oldBannerUrl, String newBannerUrl, @NonNull GuildUpdateBannerEvent event) {
}
default void onGuildUpdateDescription(@NonNull BatGuild guild, String oldDescription, String newDescription, @NonNull GuildUpdateDescriptionEvent event) {
}
default void onGuildUpdateIcon(@NonNull BatGuild guild, String oldIconUrl, String newIconUrl, @NonNull GuildUpdateIconEvent event) {
}
default void onGuildUpdateLocale(@NonNull BatGuild guild, DiscordLocale oldLocale, DiscordLocale newLocale, @NonNull GuildUpdateLocaleEvent event) {
}
default void onGuildUpdateCommunityUpdatesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel,
@NonNull GuildUpdateCommunityUpdatesChannelEvent event) {
}
default void onGuildUpdateBoostTier(@NonNull BatGuild guild, Guild.BoostTier oldTier, Guild.BoostTier newTier, @NonNull GuildUpdateBoostTierEvent event) {
}
default void onGuildUpdateMaxMembers(@NonNull BatGuild guild, int oldCount, int newCount, @NonNull GuildUpdateMaxMembersEvent event) {
}
default void onGuildUpdateExplicitContentLevel(@NonNull BatGuild guild, Guild.ExplicitContentLevel oldLevel, Guild.ExplicitContentLevel newLevel,
@NonNull GuildUpdateExplicitContentLevelEvent event) {
}
default void onGuildUpdateMFALevel(@NonNull BatGuild guild, Guild.MFALevel oldLevel, Guild.MFALevel newLevel, @NonNull GuildUpdateMFALevelEvent event) {
}
default void onGuildUpdateNotificationLevel(@NonNull BatGuild guild, Guild.NotificationLevel oldLevel, Guild.NotificationLevel newLevel,
@NonNull GuildUpdateNotificationLevelEvent event) {
}
default void onGuildUpdateNSFWLevel(@NonNull BatGuild guild, Guild.NSFWLevel oldLevel, Guild.NSFWLevel newLevel, @NonNull GuildUpdateNSFWLevelEvent event) {
}
default void onGuildUpdateOwner(@NonNull BatGuild guild, @NonNull BatUser oldOwner, @NonNull BatUser newOwner, @NonNull GuildUpdateOwnerEvent event) {
}
default void onGuildUpdateRulesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateRulesChannelEvent event) {
}
default void onGuildUpdateSystemChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateSystemChannelEvent event) {
}
default void onGuildUpdateVanityCode(@NonNull BatGuild guild, String oldCode, String newCode, @NonNull GuildUpdateVanityCodeEvent event) {
}
default void onGuildUpdateVerificationLevel(@NonNull BatGuild guild, Guild.VerificationLevel oldLevel, Guild.VerificationLevel newLevel,
@NonNull GuildUpdateVerificationLevelEvent event) {
}
default void onGuildUpdateSplash(@NonNull BatGuild guild, String oldSplashUrl, String newSplashUrl, @NonNull GuildUpdateSplashEvent event) {
}
default void onChannelUpdateArchived(@NonNull BatGuild guild, @NonNull Channel channel, boolean isArchived, @NonNull ChannelUpdateArchivedEvent event) {
}
default void onRoleUpdatePosition(@NonNull BatGuild guild, @NonNull Role role, int oldPosition, int newPosition, @NonNull RoleUpdatePositionEvent event) {
}
default void onChannelUpdatePosition(@NonNull BatGuild guild, @NonNull Channel channel, int oldPosition, int newPosition,
@NonNull ChannelUpdatePositionEvent event) {
}
default void onRoleUpdateHoisted(@NonNull BatGuild guild, @NonNull Role role, boolean wasHoisted, boolean isHoisted, @NonNull RoleUpdateHoistedEvent event) {
}
default void onShutdown() {
} }
} }

@ -0,0 +1,10 @@
package cc.fascinated.bat.exception;
/**
* @author Fascinated (fascinated7)
*/
public class BatException extends Exception {
public BatException(String message) {
super(message);
}
}

@ -0,0 +1,13 @@
package cc.fascinated.bat.exception.spotify;
import lombok.experimental.StandardException;
/**
* @author Fascinated (fascinated7)
*/
@StandardException
public class SpotifyTokenRefreshException extends RuntimeException {
public SpotifyTokenRefreshException(String message) {
super(message);
}
}

@ -1,6 +1,7 @@
package cc.fascinated.bat.features; package cc.fascinated.bat.features;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
@ -19,16 +20,16 @@ public abstract class Feature {
*/ */
private final String name; private final String name;
/**
* The default state of the feature
*/
private final FeatureProfile.FeatureState defaultState;
/** /**
* The description of the feature * The description of the feature
*/ */
public final boolean canBeDisabled; public final boolean canBeDisabled;
/**
* The category of the feature
*/
private final Category category;
/** /**
* Registers the command for the feature * Registers the command for the feature
* *
@ -36,10 +37,11 @@ public abstract class Feature {
* @param command The command to register * @param command The command to register
*/ */
public void registerCommand(@NonNull CommandService commandService, @NonNull BatCommand command) { public void registerCommand(@NonNull CommandService commandService, @NonNull BatCommand command) {
command.setFeature(this); // If the command using the default category then set the category to the feature's category
for (BatCommand subCommand : command.getSubCommands().values()) { if (command.getCategory() == Category.GENERAL) {
subCommand.setFeature(this); command.setCategory(this.category);
} }
command.setFeature(this);
commandService.registerCommand(command); commandService.registerCommand(command);
} }
} }

@ -1,7 +1,7 @@
package cc.fascinated.bat.features.afk; package cc.fascinated.bat.features.afk;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.afk.command.AfkCommand; import cc.fascinated.bat.features.afk.command.AfkCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
@Component @Component
public class AfkFeature extends Feature { public class AfkFeature extends Feature {
public AfkFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public AfkFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("AFK", FeatureProfile.FeatureState.DISABLED, true); super("AFK", true, Category.GENERAL);
registerCommand(commandService, context.getBean(AfkCommand.class)); registerCommand(commandService, context.getBean(AfkCommand.class));
} }

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.afk.command; package cc.fascinated.bat.features.afk.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.MemberUtils; import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.features.afk.profile.AfkProfile; import cc.fascinated.bat.features.afk.profile.AfkProfile;
@ -9,37 +8,24 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "afk", description = "Sets your AFK status", category = Category.GENERAL) @CommandInfo(name = "afk", description = "Sets your AFK status")
public class AfkCommand extends BatCommand { public class AfkCommand extends BatCommand {
public AfkCommand() { public AfkCommand() {
super.addOptions(new OptionData(OptionType.STRING, "reason", "The reason for being AFK", false)); super.addOption(OptionType.STRING, "reason", "The reason for being AFK", false);
} }
/**
* Fired when this command is executed.
*
* @param guild the guild the command was executed in, if any
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage
* @param arguments
* @param event the event that invoked this command
*/
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
AfkProfile profile = guild.getProfile(AfkProfile.class); AfkProfile profile = guild.getProfile(AfkProfile.class);
String reason = null; String reason = null;
OptionMapping reasonOption = event.getOption("reason"); OptionMapping reasonOption = event.getOption("reason");

@ -1,7 +1,7 @@
package cc.fascinated.bat.features.autorole; package cc.fascinated.bat.features.autorole;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.autorole.command.AutoRoleCommand; import cc.fascinated.bat.features.autorole.command.AutoRoleCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
public class AutoRoleFeature extends Feature { public class AutoRoleFeature extends Feature {
@Autowired @Autowired
public AutoRoleFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public AutoRoleFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Auto Role", FeatureProfile.FeatureState.DISABLED, true); super("Auto Role",true, Category.SERVER);
registerCommand(commandService, context.getBean(AutoRoleCommand.class)); registerCommand(commandService, context.getBean(AutoRoleCommand.class));
} }

@ -19,7 +19,7 @@ import java.util.List;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@Log4j2(topic = "AutoRole Listener") @Log4j2
public class AutoRoleListener implements EventListener { public class AutoRoleListener implements EventListener {
private final FeatureService featureService; private final FeatureService featureService;

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.RoleUtils; import cc.fascinated.bat.common.RoleUtils;
@ -9,13 +9,11 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,14 +22,14 @@ import org.springframework.stereotype.Component;
*/ */
@Component("autoroles:add.sub") @Component("autoroles:add.sub")
@CommandInfo(name = "add", description = "Adds a role to the auto roles list") @CommandInfo(name = "add", description = "Adds a role to the auto roles list")
public class AddSubCommand extends BatCommand { public class AddSubCommand extends BatSubCommand {
@Autowired @Autowired
public AddSubCommand() { public AddSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to add", true)); super.addOption(OptionType.ROLE, "role", "The role to add", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
// Check if the guild has reached the maximum auto roles count // Check if the guild has reached the maximum auto roles count
int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild); int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild);
@ -44,7 +42,12 @@ public class AddSubCommand extends BatCommand {
} }
OptionMapping option = event.getOption("role"); OptionMapping option = event.getOption("role");
assert option != null; if (option == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to add")
.build()).queue();
return;
}
Role role = option.getAsRole(); Role role = option.getAsRole();
// Check if the role is already in the auto roles list // Check if the role is already in the auto roles list

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
@ -12,20 +11,12 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component("autoroles.command") @Component("autoroles.command")
@CommandInfo( @CommandInfo(name = "autorole", description = "Set up the automatic role system for members on join", requiredPermissions = Permission.MANAGE_SERVER)
name = "autorole",
description = "Set up the automatic role system for members on join",
requiredPermissions = Permission.MANAGE_SERVER,
category = Category.SERVER
)
public class AutoRoleCommand extends BatCommand { public class AutoRoleCommand extends BatCommand {
public AutoRoleCommand(@NonNull ApplicationContext context) { public AutoRoleCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(ListSubCommand.class));
context.getBean(ListSubCommand.class), super.addSubCommand(context.getBean(AddSubCommand.class));
context.getBean(AddSubCommand.class), super.addSubCommand(context.getBean(RemoveSubCommand.class));
context.getBean(RemoveSubCommand.class), super.addSubCommand(context.getBean(ClearSubCommand.class));
context.getBean(ClearSubCommand.class),
context.getBean(SyncSubCommand.class)
);
} }
} }

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
@ -8,7 +8,6 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -18,9 +17,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component("autoroles:clear.sub") @Component("autoroles:clear.sub")
@CommandInfo(name = "clear", description = "Clears all auto roles") @CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatCommand { public class ClearSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
profile.reset(); profile.reset();

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component("autoroles:list.sub") @Component("autoroles:list.sub")
@CommandInfo(name = "list", description = "Lists all auto roles") @CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatCommand { public class ListSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) { if (profile.getRoles().isEmpty()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.autorole.command; package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
@ -8,13 +8,11 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -23,17 +21,22 @@ import org.springframework.stereotype.Component;
*/ */
@Component("autoroles:remove.sub") @Component("autoroles:remove.sub")
@CommandInfo(name = "remove", description = "Removes a role from the auto roles list") @CommandInfo(name = "remove", description = "Removes a role from the auto roles list")
public class RemoveSubCommand extends BatCommand { public class RemoveSubCommand extends BatSubCommand {
@Autowired @Autowired
public RemoveSubCommand() { public RemoveSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to remove", true)); super.addOption(OptionType.ROLE, "role", "The role to remove", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
OptionMapping option = event.getOption("role"); OptionMapping option = event.getOption("role");
assert option != null; if (option == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Please provide a role to remove")
.build()).queue();
return;
}
Role role = option.getAsRole(); Role role = option.getAsRole();
if (!profile.hasRole(role.getId())) { if (!profile.hasRole(role.getId())) {

@ -1,113 +0,0 @@
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2(topic = "AutoRole Sync SubCommand")
@Component("autoroles:sync.sub")
@CommandInfo(name = "sync", description = "Gives everyone their missing auto roles")
public class SyncSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
if (profile.getRoles().isEmpty()) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("There are no auto roles set")
.build()).queue();
return;
}
Guild discordGuild = guild.getDiscordGuild();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("Finding members that are missing auto roles...")
.build())
.queue(message -> {
log.info("Finding members that are missing auto roles in guild \"{}\"", discordGuild.getName());
List<Member> members = discordGuild.loadMembers().get();
Map<Role, Integer> rolesGiven = new HashMap<>();
// Find members that are missing the auto roles
members.removeIf(foundMember -> {
if (foundMember.getUser().isBot() || foundMember.getId().equals(discordGuild.getSelfMember().getId())) {
return true;
}
return new HashSet<>(foundMember.getRoles()).containsAll(profile.getRoles());
});
log.info("Found {} members that are missing auto roles in guild \"{}\"", members.size(), discordGuild.getName());
// No members were missing roles
if (members.isEmpty()) {
message.editOriginalEmbeds(EmbedUtils.successEmbed()
.setDescription("There are no members missing auto roles")
.build()).queue();
return;
}
int finished = 0;
for (Member foundMember : members) {
// Check if the user doesn't have the role, so we can
// show the incremented count when we're done
for (Role role : profile.getRoles()) {
if (foundMember.getRoles().contains(role)) {
continue;
}
rolesGiven.put(role, rolesGiven.getOrDefault(role, 0) + 1);
}
// Give the user the roles
discordGuild.modifyMemberRoles(foundMember, profile.getRoles(), null).queue();
finished++;
// Update the message every 10 members
if (finished % 10 == 0 || finished == 1) {
message.editOriginalEmbeds(EmbedUtils.genericEmbed()
.setDescription("""
Giving auto roles...
Completed: `%s`/`%s`
""".formatted(finished, members.size()))
.build()).queue();
}
// We're finished giving all the roles
if (finished == members.size()) {
DescriptionBuilder description = new DescriptionBuilder(
"Successfully gave auto roles to `%s` member%s".formatted(
members.size(),
members.size() == 1 ? "" : "s"
));
description.emptyLine();
description.appendLine("**Auto Roles Given:**", false);
for (Map.Entry<Role, Integer> entry : rolesGiven.entrySet()) {
description.appendLine("%s: %s member%s".formatted(
entry.getKey().getAsMention(),
entry.getValue(),
entry.getValue() == 1 ? "" : "s"
), true);
}
message.editOriginalEmbeds(EmbedUtils.successEmbed().setDescription(description.build()).build()).queue();
}
}
});
}
}

@ -1,11 +1,9 @@
package cc.fascinated.bat.features.base; package cc.fascinated.bat.features.base;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile; import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumAdminCommand;
import cc.fascinated.bat.features.base.commands.botadmin.BotAdminCommand;
import cc.fascinated.bat.features.base.commands.fun.CoinFlipCommand;
import cc.fascinated.bat.features.base.commands.fun.EightBallCommand; import cc.fascinated.bat.features.base.commands.fun.EightBallCommand;
import cc.fascinated.bat.features.base.commands.fun.PPSizeCommand;
import cc.fascinated.bat.features.base.commands.fun.image.ImageCommand; import cc.fascinated.bat.features.base.commands.fun.image.ImageCommand;
import cc.fascinated.bat.features.base.commands.general.*; import cc.fascinated.bat.features.base.commands.general.*;
import cc.fascinated.bat.features.base.commands.general.avatar.AvatarCommand; import cc.fascinated.bat.features.base.commands.general.avatar.AvatarCommand;
@ -14,8 +12,6 @@ import cc.fascinated.bat.features.base.commands.server.MemberCountCommand;
import cc.fascinated.bat.features.base.commands.server.PremiumCommand; import cc.fascinated.bat.features.base.commands.server.PremiumCommand;
import cc.fascinated.bat.features.base.commands.server.channel.ChannelCommand; import cc.fascinated.bat.features.base.commands.server.channel.ChannelCommand;
import cc.fascinated.bat.features.base.commands.server.feature.FeatureCommand; import cc.fascinated.bat.features.base.commands.server.feature.FeatureCommand;
import cc.fascinated.bat.features.base.commands.utility.PastebinCommand;
import cc.fascinated.bat.features.base.commands.utility.lookup.LookupCommand;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -29,10 +25,10 @@ import org.springframework.stereotype.Component;
public class BaseFeature extends Feature { public class BaseFeature extends Feature {
@Autowired @Autowired
public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) { public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Base", FeatureProfile.FeatureState.ENABLED, false); super("Base", false, Category.GENERAL);
super.registerCommand(commandService, context.getBean(PremiumCommand.class)); super.registerCommand(commandService, context.getBean(PremiumCommand.class));
super.registerCommand(commandService, context.getBean(BotAdminCommand.class)); super.registerCommand(commandService, context.getBean(PremiumAdminCommand.class));
super.registerCommand(commandService, context.getBean(MemberCountCommand.class)); super.registerCommand(commandService, context.getBean(MemberCountCommand.class));
super.registerCommand(commandService, context.getBean(ChannelCommand.class)); super.registerCommand(commandService, context.getBean(ChannelCommand.class));
super.registerCommand(commandService, context.getBean(VoteCommand.class)); super.registerCommand(commandService, context.getBean(VoteCommand.class));
@ -45,9 +41,5 @@ public class BaseFeature extends Feature {
super.registerCommand(commandService, context.getBean(ImageCommand.class)); super.registerCommand(commandService, context.getBean(ImageCommand.class));
super.registerCommand(commandService, context.getBean(FeatureCommand.class)); super.registerCommand(commandService, context.getBean(FeatureCommand.class));
super.registerCommand(commandService, context.getBean(EightBallCommand.class)); super.registerCommand(commandService, context.getBean(EightBallCommand.class));
super.registerCommand(commandService, context.getBean(LookupCommand.class));
super.registerCommand(commandService, context.getBean(PPSizeCommand.class));
super.registerCommand(commandService, context.getBean(PastebinCommand.class));
super.registerCommand(commandService, context.getBean(CoinFlipCommand.class));
} }
} }

@ -1,25 +0,0 @@
package cc.fascinated.bat.features.base.commands.botadmin;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumRemoveSubCommand;
import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumSetSubCommand;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "bot-admin", description = "Bot admin commands", botOwnerOnly = true)
public class BotAdminCommand extends BatCommand {
@Autowired
public BotAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(PremiumSetSubCommand.class),
context.getBean(PremiumRemoveSubCommand.class)
);
}
}

@ -0,0 +1,22 @@
package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premiumadmin", description = "Set a guild as premium", botOwnerOnly = true, category = Category.BOT_ADMIN)
public class PremiumAdminCommand extends BatCommand {
@Autowired
public PremiumAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(SetSubCommand.class));
super.addSubCommand(context.getBean(RemoveSubCommand.class));
}
}

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.botadmin.premium; package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
@ -8,12 +8,10 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,18 +19,18 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "premium-remove", description = "Remove premium from a guild") @CommandInfo(name = "remove", description = "Remove premium from a guild")
public class PremiumRemoveSubCommand extends BatCommand { public class RemoveSubCommand extends BatSubCommand {
private final GuildService guildService; private final GuildService guildService;
@Autowired @Autowired
public PremiumRemoveSubCommand(GuildService guildService) { public RemoveSubCommand(GuildService guildService) {
this.guildService = guildService; this.guildService = guildService;
super.addOptions(new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true)); super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild"); OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) { if (guildOption == null) {
event.reply("Please provide a guild id").queue(); event.reply("Please provide a guild id").queue();

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.botadmin.premium; package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
@ -8,12 +8,10 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,21 +19,19 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "premium-set", description = "Adds premium to a guild") @CommandInfo(name = "set", description = "Adds premium to a guild")
public class PremiumSetSubCommand extends BatCommand { public class SetSubCommand extends BatSubCommand {
private final GuildService guildService; private final GuildService guildService;
@Autowired @Autowired
public PremiumSetSubCommand(@NonNull GuildService guildService) { public SetSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService; this.guildService = guildService;
super.addOptions( super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true);
new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true), super.addOption(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true);
new OptionData(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true)
);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping guildOption = event.getOption("guild"); OptionMapping guildOption = event.getOption("guild");
if (guildOption == null) { if (guildOption == null) {
event.reply("Please provide a guild id").queue(); event.reply("Please provide a guild id").queue();

@ -1,40 +0,0 @@
package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* @author Fascinated (fascinated7)
*/
@CommandInfo(
name = "coinflip",
description = "Flips a coin",
userInstall = true
)
@Component
public class CoinFlipCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member,
Message commandMessage, String[] arguments, SlashCommandInteraction event) {
event.reply("%s, you flipped a coin and got **%s**!".formatted(user.getDiscordUser().getAsMention(), flip())).queue();
}
/**
* Flips a coin
*
* @return The result of the coin flip
*/
public String flip() {
return new Random().nextBoolean() ? "heads" : "tails";
}
}

@ -1,31 +1,23 @@
package cc.fascinated.bat.features.base.commands.fun; package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo( @CommandInfo(name = "8ball", description = "Ask the magic 8ball a question")
name = "8ball", description = "Ask the magic 8ball a question",
guildOnly = false,
userInstall = true,
prefixAllowed = true,
category = Category.FUN
)
public class EightBallCommand extends BatCommand { public class EightBallCommand extends BatCommand {
private final String[] responses = new String[]{ private final String[] responses = new String[]{
"It is certain", "It is certain",
@ -51,22 +43,20 @@ public class EightBallCommand extends BatCommand {
}; };
public EightBallCommand() { public EightBallCommand() {
super.addOptions(new OptionData(OptionType.STRING, "question", "The question you want to ask the 8ball", true)); super.addOption(OptionType.STRING, "question", "The question you want to ask the 8ball", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
String question = super.getArgument("question", arguments, 0, true, event).getAsString(); OptionMapping questionOption = event.getOption("question");
if (question == null) { if (questionOption == null) {
super.replyEmbed(commandMessage, event, EmbedUtils.errorEmbed()
.setDescription("You need to provide a question to ask the 8ball")
);
return; return;
} }
String question = questionOption.getAsString();
String response = responses[(int) (Math.random() * responses.length)]; String response = responses[(int) (Math.random() * responses.length)];
super.replyEmbed(commandMessage, event, EmbedUtils.successEmbed() event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response)) .setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response))
); .build())
.queue();
} }
} }

@ -1,57 +0,0 @@
package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "ppsize",
description = "Get the size of someone's pp",
userInstall = true,
category = Category.FUN
)
public class PPSizeCommand extends BatCommand {
public PPSizeCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user you want to get the pp size of", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user");
assert userOption != null; // This should never be null
User target = userOption.getAsUser();
int size = (int) MathUtils.random(1, 12);
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("PP Size")
.appendLine("""
The size of %s's pp is %s inches
**8%sD**
""".formatted(
target.getAsMention(),
size,
"=".repeat(size)
), false)
.build())
.build()).queue();
}
}

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.fun.image; package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest; import cc.fascinated.bat.common.WebRequest;
@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.thecatapi.CatImageToken; import cc.fascinated.bat.model.token.thecatapi.CatImageToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "cat", description = "Get a random cat image") @CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatCommand { public class CatSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
CatImageToken[] responseEntity = WebRequest.getAsEntity("https://api.thecatapi.com/v1/images/search", CatImageToken[].class); CatImageToken[] responseEntity = WebRequest.getAsEntity("https://api.thecatapi.com/v1/images/search", CatImageToken[].class);
if (responseEntity == null || responseEntity.length == 0) { if (responseEntity == null || responseEntity.length == 0) {
event.reply("Failed to get a cat image!").queue(); event.reply("Failed to get a cat image!").queue();

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.fun.image; package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest; import cc.fascinated.bat.common.WebRequest;
@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.dogceo.RandomImage; import cc.fascinated.bat.model.token.dogceo.RandomImage;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "dog", description = "Get a random dog image") @CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatCommand { public class DogSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
RandomImage responseEntity = WebRequest.getAsEntity("https://dog.ceo/api/breeds/image/random", RandomImage.class); RandomImage responseEntity = WebRequest.getAsEntity("https://dog.ceo/api/breeds/image/random", RandomImage.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a dog image!").queue(); event.reply("Failed to get a dog image!").queue();

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.fun.image; package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest; import cc.fascinated.bat.common.WebRequest;
@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomd.RandomDuck; import cc.fascinated.bat.model.token.randomd.RandomDuck;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "duck", description = "Get a random duck image") @CommandInfo(name = "duck", description = "Get a random duck image")
public class DuckSubCommand extends BatCommand { public class DuckSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
RandomDuck responseEntity = WebRequest.getAsEntity("https://random-d.uk/api/v2/random", RandomDuck.class); RandomDuck responseEntity = WebRequest.getAsEntity("https://random-d.uk/api/v2/random", RandomDuck.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a duck image!").queue(); event.reply("Failed to get a duck image!").queue();

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.fun.image; package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest; import cc.fascinated.bat.common.WebRequest;
@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomfox.RandomFoxToken; import cc.fascinated.bat.model.token.randomfox.RandomFoxToken;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "fox", description = "Get a random fox image") @CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatCommand { public class FoxSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
RandomFoxToken responseEntity = WebRequest.getAsEntity("https://randomfox.ca/floof/", RandomFoxToken.class); RandomFoxToken responseEntity = WebRequest.getAsEntity("https://randomfox.ca/floof/", RandomFoxToken.class);
if (responseEntity == null) { if (responseEntity == null) {
event.reply("Failed to get a fox image!").queue(); event.reply("Failed to get a fox image!").queue();

@ -12,15 +12,13 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "image", description = "View a random image", guildOnly = false, userInstall = true, category = Category.FUN) @CommandInfo(name = "image", description = "View a random image", guildOnly = false, category = Category.FUN)
public class ImageCommand extends BatCommand { public class ImageCommand extends BatCommand {
@Autowired @Autowired
public ImageCommand(@NonNull ApplicationContext context) { public ImageCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(CatSubCommand.class));
context.getBean(CatSubCommand.class), super.addSubCommand(context.getBean(DogSubCommand.class));
context.getBean(DogSubCommand.class), super.addSubCommand(context.getBean(FoxSubCommand.class));
context.getBean(FoxSubCommand.class), super.addSubCommand(context.getBean(DuckSubCommand.class));
context.getBean(DuckSubCommand.class)
);
} }
} }

@ -1,9 +1,11 @@
package cc.fascinated.bat.features.base.commands.general; package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.*; import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.common.TimeUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
@ -12,7 +14,6 @@ import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -25,12 +26,11 @@ import java.lang.management.RuntimeMXBean;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "botstats", description = "Shows the bot statistics", guildOnly = false, userInstall = true, category = Category.GENERAL) @CommandInfo(name = "botstats", description = "Shows the bot statistics", guildOnly = false)
public class BotStatsCommand extends BatCommand { public class BotStatsCommand extends BatCommand {
private final GuildService guildService; private final GuildService guildService;
private final UserService userService; private final UserService userService;
private final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); private final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
private final Runtime runtime = Runtime.getRuntime();
@Autowired @Autowired
public BotStatsCommand(@NonNull GuildService guildService, @NonNull UserService userService) { public BotStatsCommand(@NonNull GuildService guildService, @NonNull UserService userService) {
@ -39,19 +39,17 @@ public class BotStatsCommand extends BatCommand {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
JDA jda = DiscordService.JDA; JDA jda = DiscordService.JDA;
long memoryUsed = (runtime.totalMemory() - runtime.freeMemory());
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription( event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
new DescriptionBuilder("Bat Statistics") new EmbedDescriptionBuilder("Bat Statistics")
.appendLine("Guilds: **%s**".formatted(NumberFormatter.format(jda.getGuilds().size())), true) .appendLine("Guilds: **%s**".formatted(NumberFormatter.format(jda.getGuilds().size())), true)
.appendLine("Users: **%s**".formatted(NumberFormatter.format(jda.getUsers().size())), true) .appendLine("Users: **%s**".formatted(NumberFormatter.format(jda.getUsers().size())), true)
.appendLine("Gateway Ping: **%sms**".formatted(jda.getGatewayPing()), true) .appendLine("Gateway Ping: **%sms**".formatted(jda.getGatewayPing()), true)
.emptyLine() .emptyLine()
.appendSubtitle("Bot Statistics") .appendSubtitle("Bot Statistics")
.appendLine("Uptime: **%s**".formatted(TimeUtils.format(bean.getUptime())), true) .appendLine("Uptime: **%s**".formatted(TimeUtils.format(bean.getUptime())), true)
.appendLine("Memory Usage: **%s**".formatted(StringUtils.formatBytes(memoryUsed)), true)
.appendLine("Cached Guilds: **%s**".formatted(NumberFormatter.format(guildService.getGuilds().size())), true) .appendLine("Cached Guilds: **%s**".formatted(NumberFormatter.format(guildService.getGuilds().size())), true)
.appendLine("Cached Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size())), true) .appendLine("Cached Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size())), true)
.build() .build()

@ -1,38 +1,41 @@
package cc.fascinated.bat.features.base.commands.general; package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts; import cc.fascinated.bat.Consts;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category; import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.InteractionBuilder;
import cc.fascinated.bat.event.EventListener; import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.CommandService; import cc.fascinated.bat.service.CommandService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; 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.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
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.buttons.ButtonStyle;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo( @CommandInfo(name = "help", description = "View the bots command categories.", guildOnly = false)
name = "help",
description = "View the bots command categories",
userInstall = true,
category = Category.GENERAL
)
public class HelpCommand extends BatCommand implements EventListener { public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService; private final CommandService commandService;
@ -42,53 +45,61 @@ public class HelpCommand extends BatCommand implements EventListener {
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
InteractionBuilder interactionBuilder = new InteractionBuilder(); event.replyEmbeds(createHomeEmbed()).addComponents(createHomeActions()).queue();
interactionBuilder.addUrlButton("Invite Me", Consts.INVITE_URL, null); }
interactionBuilder.addUrlButton("Support Server", Consts.SUPPORT_INVITE_URL, null);
interactionBuilder.addStringSelect("Home", "Return Home", Emojis.HOME_EMOJI, (buttonEvent) -> {
buttonEvent.editMessageEmbeds(createHomeEmbed(event.isFromGuild())).queue();
});
for (Category category : Category.getSortedByName()) {
List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, event.isFromGuild());
if (categoryCommands.isEmpty()) {
continue;
}
interactionBuilder.addStringSelect( @Override
category.getName(), public void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {
"View commands in the %s category".formatted(category.getName()), String item = event.getSelectedOptions().get(0).getValue();
category.getEmoji(), if (item.equalsIgnoreCase("home")) {
(buttonEvent) -> { event.editMessageEmbeds(createHomeEmbed()).queue();
DescriptionBuilder description = new DescriptionBuilder(null); return;
description.appendLine("Commands in the **%s** Category".formatted(category.getName()), false);
description.emptyLine();
for (BatCommand command : categoryCommands) {
if (!command.getSubCommands().isEmpty()) {
for (BatCommand subCommand : command.getSubCommands().values()) {
description.appendLine("`%s %s` - %s".formatted(
command.getInfo().getName(),
subCommand.getInfo().getName(),
subCommand.getInfo().getDescription()
), true);
}
continue;
}
description.appendLine("`%s` - %s".formatted(
command.getInfo().getName(),
command.getInfo().getDescription()
), true);
}
buttonEvent.editMessageEmbeds(EmbedUtils.genericEmbed()
.setDescription(description.build())
.build()).queue();
});
} }
Category category = Category.getByName(item);
if (category == null) {
event.reply("Invalid category selected.").queue();
return;
}
event.replyEmbeds(createHomeEmbed(event.isFromGuild())).addComponents(interactionBuilder.build()).queue(); StringBuilder commands = new StringBuilder();
List<BatCommand> categoryCommands = commandService.getCommandsByCategory(category, true);
if (categoryCommands.isEmpty()) {
commands = new StringBuilder("No commands available in this category.");
} else {
for (BatCommand command : categoryCommands) {
if (!command.getSubCommands().isEmpty()) {
for (Map.Entry<String, BatSubCommand> entry : command.getSubCommands().entrySet()) {
BatSubCommand subCommand = entry.getValue();
SubcommandData commandData = subCommand.getCommandData();
commands.append("</%s %s:%s> - %s\n".formatted(
command.getCommandInfo().name(),
commandData.getName(),
subCommand.getCommandSnowflake(),
commandData.getDescription()
));
}
continue;
}
commands.append("</%s:%s> - %s\n".formatted(
command.getCommandInfo().name(),
command.getCommandSnowflake(),
command.getCommandInfo().description()
));
}
}
int subCommands = categoryCommands.stream().mapToInt(command -> command.getSubCommands().size()).sum();
event.editMessageEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s Category".formatted(category.getName()))
.setDescription("%s command%s (with %s sub-command%s)\n\n**Commands:**\n%s".formatted(
categoryCommands.size(),
categoryCommands.size() == 1 ? "" : "s",
subCommands,
subCommands == 1 ? "" : "s",
commands.toString()
)).build()).queue();
} }
/** /**
@ -96,13 +107,10 @@ public class HelpCommand extends BatCommand implements EventListener {
* *
* @return The home embed * @return The home embed
*/ */
private MessageEmbed createHomeEmbed(boolean ranInsideGuild) { private MessageEmbed createHomeEmbed() {
StringBuilder categories = new StringBuilder(); StringBuilder categories = new StringBuilder();
for (Category category : Category.values()) { for (Category category : Category.getCategories()) {
if (commandService.getCommandsByCategory(category, ranInsideGuild).isEmpty()) { long commandCount = commandService.getCommandsByCategory(category, true).size();
continue;
}
long commandCount = commandService.getCommandsByCategory(category, ranInsideGuild).size();
categories.append("➜ %s - **%s Command%s**\n".formatted( categories.append("➜ %s - **%s Command%s**\n".formatted(
category.getName(), category.getName(),
commandCount, commandCount,
@ -112,16 +120,40 @@ public class HelpCommand extends BatCommand implements EventListener {
return EmbedUtils.genericEmbed() return EmbedUtils.genericEmbed()
.setDescription(""" .setDescription("""
**Welcome to the Bat Help Menu!**%s **Welcome to the Bat Help Menu!**
%s %s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.* *View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted( """.formatted(
!ranInsideGuild ? "\n*guild only commands won't be shown here*" : "",
categories.toString(), categories.toString(),
Consts.TERMS_OF_SERVICE_URL, Consts.TERMS_OF_SERVICE_URL,
Consts.PRIVACY_POLICY_URL Consts.PRIVACY_POLICY_URL
)) ))
.build(); .build();
} }
/**
* Creates the home actions for the help command
*
* @return The layout components
*/
private LayoutComponent[] createHomeActions() {
List<SelectOption> options = new ArrayList<>();
options.add(SelectOption.of("Home", "home").withEmoji(Emoji.fromUnicode("U+1F3E0")));
options.addAll(Category.getCategories().stream().map(category ->
SelectOption.of(category.getName(), category.getName()).withEmoji(category.getEmoji()))
.toList());
return new LayoutComponent[]{
ActionRow.of(
Button.of(ButtonStyle.LINK, Consts.INVITE_URL, "Invite"),
Button.of(ButtonStyle.LINK, Consts.SUPPORT_INVITE_URL, "Support")
),
ActionRow.of(
StringSelectMenu.create("help-menu")
.addOptions(options)
.build()
)
};
}
} }

@ -2,14 +2,12 @@ package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts; import cc.fascinated.bat.Consts;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -18,10 +16,10 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "invite", description = "Invite the bot to your server!", guildOnly = false, category = Category.GENERAL) @CommandInfo(name = "invite", description = "Invite the bot to your server!", guildOnly = false)
public class InviteCommand extends BatCommand { public class InviteCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
event.replyEmbeds(EmbedUtils.genericEmbed() event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL)) .setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL))
.build()) .build())

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general; package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -17,16 +15,10 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo( @CommandInfo(name = "ping", description = "Gets the ping of the bot", guildOnly = false)
name = "ping",
description = "Gets the ping of the bot",
guildOnly = false,
userInstall = true,
category = Category.GENERAL
)
public class PingCommand extends BatCommand { public class PingCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
event.reply("Pinging...").queue(response -> { event.reply("Pinging...").queue(response -> {
response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted( response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted(

@ -1,15 +1,13 @@
package cc.fascinated.bat.features.base.commands.general; package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder; import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -18,7 +16,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "vote", description = "Vote for the bot", guildOnly = false, userInstall = true, category = Category.GENERAL) @CommandInfo(name = "vote", description = "Vote for the bot", guildOnly = false)
public class VoteCommand extends BatCommand { public class VoteCommand extends BatCommand {
private static final String[] VOTE_LINKS = new String[]{ private static final String[] VOTE_LINKS = new String[]{
"https://top.gg/bot/1254161119975833652/vote", "https://top.gg/bot/1254161119975833652/vote",
@ -26,8 +24,8 @@ public class VoteCommand extends BatCommand {
}; };
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
DescriptionBuilder description = new DescriptionBuilder("Vote Links"); EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Vote Links");
description.appendLine("Vote for the bot on the following websites to support us!", false); description.appendLine("Vote for the bot on the following websites to support us!", false);
for (String link : VOTE_LINKS) { for (String link : VOTE_LINKS) {
description.appendLine(link, true); description.appendLine(link, true);

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.base.commands.general.avatar; package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,13 +11,11 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @Component
@CommandInfo(name = "avatar", description = "View the avatar of the guild or a user", guildOnly = false, category = Category.GENERAL) @CommandInfo(name = "avatar", description = "View the avatar of the guild or a user", guildOnly = false)
public class AvatarCommand extends BatCommand { public class AvatarCommand extends BatCommand {
@Autowired @Autowired
public AvatarCommand(@NonNull ApplicationContext context) { public AvatarCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(GuildSubCommand.class));
context.getBean(GuildSubCommand.class), super.addSubCommand(context.getBean(UserSubCommand.class));
context.getBean(UserSubCommand.class)
);
} }
} }

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.avatar; package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.utils.ImageProxy; import net.dv8tion.jda.api.utils.ImageProxy;
@ -18,11 +16,12 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("avatar:guild.sub") @Component("avatar:guild.sub")
@CommandInfo(name = "guild", description = "View the avatar of the guild", category = Category.GENERAL) @CommandInfo(name = "guild", description = "View the avatar of the guild")
public class GuildSubCommand extends BatCommand { public class GuildSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
ImageProxy icon = guild.getDiscordGuild().getIcon(); ImageProxy icon = guild.getDiscordGuild().getIcon();
if (icon == null) { if (icon == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have an avatar!".formatted(guild.getName())) .setDescription("**%s** does not have an avatar!".formatted(guild.getName()))

@ -1,38 +1,41 @@
package cc.fascinated.bat.features.base.commands.general.avatar; package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("avatar:user.sub") @Component("avatar:user.sub")
@CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false, category = Category.GENERAL) @CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false)
public class UserSubCommand extends BatCommand { public class UserSubCommand extends BatSubCommand {
public UserSubCommand() { public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the avatar of", true)); super.addOption(OptionType.USER, "user", "The user to view the avatar of", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user"); OptionMapping userOption = event.getOption("user");
assert userOption != null; if (userOption == null) {
User target = userOption.getAsUser(); event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a user to view the avatar of!")
.build())
.queue();
return;
}
User target = userOption.getAsUser();
event.replyEmbeds(EmbedUtils.genericEmbed() event.replyEmbeds(EmbedUtils.genericEmbed()
.setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl()) .setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl())
.setImage(target.getEffectiveAvatarUrl()) .setImage(target.getEffectiveAvatarUrl())

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,13 +11,11 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @Component
@CommandInfo(name = "banner", description = "View the banner of the guild or a user", guildOnly = false, category = Category.GENERAL) @CommandInfo(name = "banner", description = "View the banner of the guild or a user", guildOnly = false)
public class BannerCommand extends BatCommand { public class BannerCommand extends BatCommand {
@Autowired @Autowired
public BannerCommand(@NonNull ApplicationContext context) { public BannerCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(GuildSubCommand.class));
context.getBean(GuildSubCommand.class), super.addSubCommand(context.getBean(UserSubCommand.class));
context.getBean(UserSubCommand.class)
);
} }
} }

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.utils.ImageProxy; import net.dv8tion.jda.api.utils.ImageProxy;
@ -18,10 +16,10 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("banner:guild.sub") @Component("banner:guild.sub")
@CommandInfo(name = "guild", description = "View the banner of the guild", category = Category.GENERAL) @CommandInfo(name = "guild", description = "View the banner of the guild")
public class GuildSubCommand extends BatCommand { public class GuildSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
ImageProxy banner = guild.getDiscordGuild().getBanner(); ImageProxy banner = guild.getDiscordGuild().getBanner();
if (banner == null) { if (banner == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,20 +1,17 @@
package cc.fascinated.bat.features.base.commands.general.banner; package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.utils.ImageProxy; import net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -22,16 +19,23 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component("banner:user.sub") @Component("banner:user.sub")
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false, category = Category.GENERAL) @CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false)
public class UserSubCommand extends BatCommand { public class UserSubCommand extends BatSubCommand {
public UserSubCommand() { public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the banner of", true)); super.addOption(OptionType.USER, "user", "The user to view the banner of", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping userOption = event.getOption("user"); OptionMapping userOption = event.getOption("user");
assert userOption != null; if (userOption == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a user to view the banner of!")
.build())
.queue();
return;
}
User target = userOption.getAsUser(); User target = userOption.getAsUser();
ImageProxy banner = target.retrieveProfile().complete().getBanner(); ImageProxy banner = target.retrieveProfile().complete().getBanner();
if (banner == null) { if (banner == null) {

@ -1,5 +1,6 @@
package cc.fascinated.bat.features.base.commands.server; package cc.fascinated.bat.features.base.commands.server;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
@ -7,13 +8,16 @@ import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@ -21,10 +25,14 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "membercount", description = "View the member count of the server!") @CommandInfo(name = "membercount", description = "View the member count of the server!")
public class MemberCountCommand extends BatCommand { public class MemberCountCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
Guild discordGuild = guild.getDiscordGuild(); Guild discordGuild = guild.getDiscordGuild();
int totalMembers = 0, totalUsers = 0, totalBots = 0; int totalMembers = 0, totalUsers = 0, totalBots = 0;
Map<OnlineStatus, Integer> memberCounts = new HashMap<>();
for (Member guildMember : discordGuild.getMembers()) { for (Member guildMember : discordGuild.getMembers()) {
OnlineStatus status = guildMember.getOnlineStatus();
memberCounts.put(status, memberCounts.getOrDefault(status, 0) + 1);
if (guildMember.getUser().isBot()) { if (guildMember.getUser().isBot()) {
totalBots++; totalBots++;
} else { } else {
@ -38,10 +46,23 @@ public class MemberCountCommand extends BatCommand {
Total Members: `%s` Total Members: `%s`
Total Users: `%s` Total Users: `%s`
Total Bots: `%s` Total Bots: `%s`
""".formatted( \s
**Member Presence**
%s Online: `%s`
%s Idle: `%s`
%s Do Not Disturb: `%s`
%s Offline: `%s`""".formatted(
NumberFormatter.format(totalMembers), NumberFormatter.format(totalMembers),
NumberFormatter.format(totalUsers), NumberFormatter.format(totalUsers),
NumberFormatter.format(totalBots) NumberFormatter.format(totalBots),
)).build()).queue(); Emojis.ONLINE_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.ONLINE, 0)),
Emojis.IDLE_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.IDLE, 0)),
Emojis.DND_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.DO_NOT_DISTURB, 0)),
Emojis.OFFLINE_EMOJI,
NumberFormatter.format(memberCounts.getOrDefault(OnlineStatus.OFFLINE, 0))))
.build()).queue();
} }
} }

@ -10,7 +10,6 @@ import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -22,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR) @CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR)
public class PremiumCommand extends BatCommand { public class PremiumCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
PremiumProfile premium = guild.getPremiumProfile(); PremiumProfile premium = guild.getPremiumProfile();
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information"); EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
if (premium.hasPremium()) { if (premium.hasPremium()) {

@ -16,10 +16,8 @@ import org.springframework.stereotype.Component;
public class ChannelCommand extends BatCommand { public class ChannelCommand extends BatCommand {
@Autowired @Autowired
public ChannelCommand(@NonNull ApplicationContext context) { public ChannelCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(ViewTopicSubCommand.class));
context.getBean(ViewTopicSubCommand.class), super.addSubCommand(context.getBean(SetTopicSubCommand.class));
context.getBean(SetTopicSubCommand.class), super.addSubCommand(context.getBean(RemoveTopicSubCommand.class));
context.getBean(RemoveTopicSubCommand.class)
);
} }
} }

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.server.channel; package cc.fascinated.bat.features.base.commands.server.channel;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
@ -8,13 +8,11 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -22,13 +20,13 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "removetopic", description = "Remove the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL) @CommandInfo(name = "removetopic", description = "Remove the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL)
public class RemoveTopicSubCommand extends BatCommand { public class RemoveTopicSubCommand extends BatSubCommand {
public RemoveTopicSubCommand() { public RemoveTopicSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to remove the topic of", false)); super.addOption(OptionType.CHANNEL, "channel", "The channel to remove the topic of", false);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel(); Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.base.commands.server.channel; package cc.fascinated.bat.features.base.commands.server.channel;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
@ -8,33 +8,27 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Objects;
/** /**
* @author Nick (okNick) * @author Nick (okNick)
*/ */
@Component @Component
@CommandInfo(name = "settopic", description = "Set the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL) @CommandInfo(name = "settopic", description = "Set the topic of a channel", requiredPermissions = Permission.MANAGE_CHANNEL)
public class SetTopicSubCommand extends BatCommand { public class SetTopicSubCommand extends BatSubCommand {
public SetTopicSubCommand() { public SetTopicSubCommand() {
super.addOptions( super.addOption(OptionType.STRING, "topic", "The topic to set", true);
new OptionData(OptionType.STRING, "topic", "The topic to set", true), super.addOption(OptionType.CHANNEL, "channel", "The channel to set the topic of", false);
new OptionData(OptionType.CHANNEL, "channel", "The channel to set the topic of", false)
);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : Objects.requireNonNull(event.getOption("channel")).getAsChannel(); Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("<#%s> is not a text channel!".formatted(target.getId())) .setDescription("<#%s> is not a text channel!".formatted(target.getId()))
@ -43,7 +37,7 @@ public class SetTopicSubCommand extends BatCommand {
return; return;
} }
String topic = Objects.requireNonNull(event.getOption("topic")).getAsString(); String topic = event.getOption("topic").getAsString();
if (topic.length() > 1024) { if (topic.length() > 1024) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The topic must be 1024 characters or less!") .setDescription("The topic must be 1024 characters or less!")
@ -51,8 +45,11 @@ public class SetTopicSubCommand extends BatCommand {
.queue(); .queue();
return; return;
} }
event.deferReply().queue(hook -> textChannel.getManager().setTopic(topic).queue((voidd) -> hook.editOriginalEmbeds(EmbedUtils.successEmbed()
textChannel.getManager().setTopic(topic).queue();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the topic of <#%s> to: \"%s\"".formatted(textChannel.getId(), topic)) .setDescription("Successfully set the topic of <#%s> to: \"%s\"".formatted(textChannel.getId(), topic))
.build()).queue())); .build()
).queue();
} }
} }

@ -1,19 +1,17 @@
package cc.fascinated.bat.features.base.commands.server.channel; package cc.fascinated.bat.features.base.commands.server.channel;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -21,13 +19,13 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
@CommandInfo(name = "viewtopic", description = "View the topic of a channel") @CommandInfo(name = "viewtopic", description = "View the topic of a channel")
public class ViewTopicSubCommand extends BatCommand { public class ViewTopicSubCommand extends BatSubCommand {
public ViewTopicSubCommand() { public ViewTopicSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to view the topic of", false)); super.addOption(OptionType.CHANNEL, "channel", "The channel to view the topic of", false);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel(); Channel target = event.getOption("channel") == null ? channel : event.getOption("channel").getAsChannel();
if (!(target instanceof TextChannel textChannel)) { if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,21 +1,19 @@
package cc.fascinated.bat.features.base.commands.server.feature; package cc.fascinated.bat.features.base.commands.server.feature;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile; import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,14 +22,14 @@ import org.springframework.stereotype.Component;
*/ */
@Component("feature:disable.sub") @Component("feature:disable.sub")
@CommandInfo(name = "disable", description = "Disables a feature") @CommandInfo(name = "disable", description = "Disables a feature")
public class DisableSubCommand extends BatCommand { public class DisableSubCommand extends BatSubCommand {
@Autowired @Autowired
public DisableSubCommand() { public DisableSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "feature", "The feature to disable", true)); super.addOption(OptionType.STRING, "feature", "The feature to disable", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature"); OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) { if (featureOption == null) {

@ -1,21 +1,19 @@
package cc.fascinated.bat.features.base.commands.server.feature; package cc.fascinated.bat.features.base.commands.server.feature;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile; import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,14 +22,14 @@ import org.springframework.stereotype.Component;
*/ */
@Component("feature:enable.sub") @Component("feature:enable.sub")
@CommandInfo(name = "enable", description = "Enables a feature") @CommandInfo(name = "enable", description = "Enables a feature")
public class EnableSubCommand extends BatCommand { public class EnableSubCommand extends BatSubCommand {
@Autowired @Autowired
public EnableSubCommand() { public EnableSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "feature", "The feature to enable", true)); super.addOption(OptionType.STRING, "feature", "The feature to enable", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();
OptionMapping featureOption = event.getOption("feature"); OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) { if (featureOption == null) {

@ -17,10 +17,8 @@ import org.springframework.stereotype.Component;
public class FeatureCommand extends BatCommand { public class FeatureCommand extends BatCommand {
@Autowired @Autowired
public FeatureCommand(@NonNull ApplicationContext context) { public FeatureCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(EnableSubCommand.class));
context.getBean(EnableSubCommand.class), super.addSubCommand(context.getBean(DisableSubCommand.class));
context.getBean(DisableSubCommand.class), super.addSubCommand(context.getBean(ListSubCommand.class));
context.getBean(ListSubCommand.class)
);
} }
} }

@ -1,16 +1,15 @@
package cc.fascinated.bat.features.base.commands.server.feature; package cc.fascinated.bat.features.base.commands.server.feature;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile; import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -20,9 +19,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component("feature:list.sub") @Component("feature:list.sub")
@CommandInfo(name = "list", description = "Lists the features and their states") @CommandInfo(name = "list", description = "Lists the features and their states")
public class ListSubCommand extends BatCommand { public class ListSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
StringBuilder featureStates = new StringBuilder(); StringBuilder featureStates = new StringBuilder();
for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) { for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) {
FeatureProfile featureProfile = guild.getFeatureProfile(); FeatureProfile featureProfile = guild.getFeatureProfile();

@ -1,54 +0,0 @@
package cc.fascinated.bat.features.base.commands.utility;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "pastebin",
description = "Uploads the given text to Paste",
userInstall = true,
category = Category.UTILITY
)
public class PastebinCommand extends BatCommand {
public PastebinCommand() {
super.addOptions(
new OptionData(OptionType.STRING, "text", "The text to upload to Paste", true)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping textOption = event.getOption("text");
assert textOption != null;
String text = textOption.getAsString();
// Upload the text to pastebin
String url = PasteUtils.uploadPaste(text).getUrl();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(new DescriptionBuilder("The text has been uploaded to Paste!")
.appendLine("URL: %s".formatted(url), true)
.build())
.build())
.setEphemeral(true)
.queue();
}
}

@ -1,28 +0,0 @@
package cc.fascinated.bat.features.base.commands.utility.lookup;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Nick (okNick)
*/
@Component
@CommandInfo(
name = "lookup",
description = "View the banner of the guild or a user",
userInstall = true,
category = Category.UTILITY
)
public class LookupCommand extends BatCommand {
@Autowired
public LookupCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(UserSubCommand.class)
);
}
}

@ -1,75 +0,0 @@
package cc.fascinated.bat.features.base.commands.utility.lookup;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("lookup.user:sub")
@CommandInfo(name = "user", description = "Lookup a user")
public class UserSubCommand extends BatCommand {
private final UserService userService;
@Autowired
public UserSubCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(new OptionData(OptionType.STRING, "id", "The id of the user", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping idOption = event.getOption("id");
if (idOption == null) {
return;
}
User target = userService.getUser(idOption.getAsString()).getDiscordUser();
if (target == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("User `%s` not found".formatted(idOption.getAsString()))
.build())
.setEphemeral(true)
.queue();
return;
}
// The flags of the user (eg. Discord Partner, Hypesquad Events, etc.)
StringBuilder flags = new StringBuilder();
for (User.UserFlag flag : target.getFlags()) {
flags.append("`").append(flag.getName()).append("`, ");
}
String name = target.getGlobalName() == null ? target.getName() : target.getGlobalName().replaceAll("`", "");
target.retrieveProfile().queue(profile -> event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("User Lookup")
.appendLine("Name: `%s`".formatted(name), true)
.appendLine("Username: `%s`".formatted(target.getName()), true)
.appendLine("ID: `%s`".formatted(target.getId()), true)
.appendLine("Flags: %s".formatted(flags.toString().isEmpty() ? "None" : flags.substring(0, flags.length() - 2)), true)
.appendLine("Joined Discord: <t:%s:R>".formatted(target.getTimeCreated().toEpochSecond()), true)
.appendLine("Avatar: %s".formatted(target.getAvatar() == null ? "None"
: "[click here](%s)".formatted(target.getAvatar().getUrl(4096))), true)
.appendLine("Banner: %s".formatted(profile.getBanner() == null ? "None"
: "[click here](%s)".formatted(profile.getBanner().getUrl(4096))), true)
.build())
.setThumbnail(target.getAvatar() == null ? null : target.getAvatar().getUrl(4096))
.build())
.setEphemeral(true)
.queue());
}
}

@ -1,11 +1,11 @@
package cc.fascinated.bat.features; package cc.fascinated.bat.features.base.profile;
import cc.fascinated.bat.common.Serializable; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.Feature;
import com.google.gson.Gson; import com.google.gson.Gson;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.bson.Document; import org.bson.Document;
import java.util.HashMap; import java.util.HashMap;
@ -16,6 +16,8 @@ import java.util.Map;
*/ */
@NoArgsConstructor @NoArgsConstructor
public class FeatureProfile extends Serializable { public class FeatureProfile extends Serializable {
private static final FeatureState DEFAULT_STATE = FeatureState.ENABLED;
/** /**
* The feature states * The feature states
*/ */
@ -26,13 +28,13 @@ public class FeatureProfile extends Serializable {
* *
* @return the feature states * @return the feature states
*/ */
public FeatureState getFeatureState(@NonNull Feature feature) { public FeatureState getFeatureState(Feature feature) {
if (!feature.isCanBeDisabled()) { if (feature == null) {
return FeatureProfile.FeatureState.ENABLED; return DEFAULT_STATE;
} }
String featureName = feature.getName().toUpperCase(); String featureName = feature.getName().toUpperCase();
if (!this.featureStates.containsKey(featureName)) { if (!this.featureStates.containsKey(featureName)) {
this.featureStates.put(featureName, feature.getDefaultState()); this.featureStates.put(featureName, DEFAULT_STATE);
} }
return this.featureStates.get(featureName); return this.featureStates.get(featureName);
} }

@ -1,8 +1,8 @@
package cc.fascinated.bat.features.birthday; package cc.fascinated.bat.features.birthday;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.event.EventListener; import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.birthday.command.BirthdayCommand; import cc.fascinated.bat.features.birthday.command.BirthdayCommand;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
@ -23,7 +23,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private final GuildService guildService; private final GuildService guildService;
public BirthdayFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) { public BirthdayFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) {
super("Birthday", FeatureProfile.FeatureState.DISABLED, true); super("Birthday", true, Category.UTILITY);
this.guildService = guildService; this.guildService = guildService;
registerCommand(commandService, context.getBean(BirthdayCommand.class)); registerCommand(commandService, context.getBean(BirthdayCommand.class));
@ -36,7 +36,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private void checkBirthdays() { private void checkBirthdays() {
for (Guild guild : DiscordService.JDA.getGuilds()) { for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId()); BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled if (batGuild == null) {
continue; continue;
} }
BirthdayProfile profile = batGuild.getBirthdayProfile(); BirthdayProfile profile = batGuild.getBirthdayProfile();

@ -58,5 +58,7 @@ public class UserBirthday extends Serializable {
} }
@Override @Override
public void reset() {} public void reset() {
}
} }

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull; import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -12,17 +11,15 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
@CommandInfo(name = "birthday", description = "Modify your birthday settings.", category = Category.UTILITY) @CommandInfo(name = "birthday", description = "Modify your birthday settings.")
public class BirthdayCommand extends BatCommand { public class BirthdayCommand extends BatCommand {
@Autowired @Autowired
public BirthdayCommand(@NonNull ApplicationContext context) { public BirthdayCommand(@NonNull ApplicationContext context) {
super.addSubCommands( super.addSubCommand(context.getBean(SetSubCommand.class));
context.getBean(SetSubCommand.class), super.addSubCommand(context.getBean(RemoveSubCommand.class));
context.getBean(RemoveSubCommand.class), super.addSubCommand(context.getBean(ChannelSubCommand.class));
context.getBean(ChannelSubCommand.class), super.addSubCommand(context.getBean(MessageSubCommand.class));
context.getBean(MessageSubCommand.class), super.addSubCommand(context.getBean(ViewSubCommand.class));
context.getBean(ViewSubCommand.class), super.addSubCommand(context.getBean(PrivateSubCommand.class));
context.getBean(PrivateSubCommand.class)
);
} }
} }

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TextChannelUtils; import cc.fascinated.bat.common.TextChannelUtils;
@ -10,14 +10,12 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -26,14 +24,14 @@ import org.springframework.stereotype.Component;
*/ */
@Component("birthday:channel.sub") @Component("birthday:channel.sub")
@CommandInfo(name = "channel", description = "Sets the birthday notification channel", requiredPermissions = Permission.MANAGE_SERVER) @CommandInfo(name = "channel", description = "Sets the birthday notification channel", requiredPermissions = Permission.MANAGE_SERVER)
public class ChannelSubCommand extends BatCommand { public class ChannelSubCommand extends BatSubCommand {
@Autowired @Autowired
public ChannelSubCommand() { public ChannelSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false)); super.addOption(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping option = event.getOption("channel"); OptionMapping option = event.getOption("channel");
if (option == null) { if (option == null) {

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
@ -9,12 +9,10 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,19 +22,24 @@ import org.springframework.stereotype.Component;
*/ */
@Component("birthday:message.sub") @Component("birthday:message.sub")
@CommandInfo(name = "message", description = "Changes the message that is sent when it is a user's birthday", requiredPermissions = Permission.MANAGE_SERVER) @CommandInfo(name = "message", description = "Changes the message that is sent when it is a user's birthday", requiredPermissions = Permission.MANAGE_SERVER)
public class MessageSubCommand extends BatCommand { public class MessageSubCommand extends BatSubCommand {
@Autowired @Autowired
public MessageSubCommand() { public MessageSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true)); super.addOption(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping messageOption = event.getOption("message"); OptionMapping messageOption = event.getOption("message");
assert messageOption != null; if (messageOption == null) {
String message = messageOption.getAsString(); event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a message")
.build()).queue();
return;
}
String message = messageOption.getAsString();
if (message.length() > 2000) { if (message.length() > 2000) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The message must be less than 2000 characters") .setDescription("The message must be less than 2000 characters")

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday; import cc.fascinated.bat.features.birthday.UserBirthday;
@ -9,12 +9,10 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -24,19 +22,24 @@ import org.springframework.stereotype.Component;
*/ */
@Component("birthday:private.sub") @Component("birthday:private.sub")
@CommandInfo(name = "private", description = "Changes whether your birthday is private or not") @CommandInfo(name = "private", description = "Changes whether your birthday is private or not")
public class PrivateSubCommand extends BatCommand { public class PrivateSubCommand extends BatSubCommand {
@Autowired @Autowired
public PrivateSubCommand() { public PrivateSubCommand() {
super.addOptions(new OptionData(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true)); super.addOption(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping enabledOption = event.getOption("enabled"); OptionMapping enabledOption = event.getOption("enabled");
assert enabledOption != null; if (enabledOption == null) {
boolean enabled = enabledOption.getAsBoolean(); event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide whether your birthday is private or not")
.build()).queue();
return;
}
boolean enabled = enabledOption.getAsBoolean();
UserBirthday birthday = profile.getBirthday(user.getId()); UserBirthday birthday = profile.getBirthday(user.getId());
if (birthday == null) { if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
@ -8,7 +8,6 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/ */
@Component("birthday:remove.sub") @Component("birthday:remove.sub")
@CommandInfo(name = "remove", description = "Remove your birthday from this guild") @CommandInfo(name = "remove", description = "Remove your birthday from this guild")
public class RemoveSubCommand extends BatCommand { public class RemoveSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
profile.removeBirthday(user.getId()); profile.removeBirthday(user.getId());

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday; import cc.fascinated.bat.features.birthday.UserBirthday;
@ -9,12 +9,10 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -28,16 +26,16 @@ import java.util.Date;
*/ */
@Component("birthday:add.sub") @Component("birthday:add.sub")
@CommandInfo(name = "set", description = "Add your birthday to this guild") @CommandInfo(name = "set", description = "Add your birthday to this guild")
public class SetSubCommand extends BatCommand { public class SetSubCommand extends BatSubCommand {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy"); private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy");
@Autowired @Autowired
public SetSubCommand() { public SetSubCommand() {
super.addOptions(new OptionData(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true)); super.addOption(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()
@ -47,9 +45,13 @@ public class SetSubCommand extends BatCommand {
} }
OptionMapping birthdayOption = event.getOption("birthday"); OptionMapping birthdayOption = event.getOption("birthday");
assert birthdayOption != null; if (birthdayOption == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a birthday")
.build()).queue();
return;
}
String birthdayString = birthdayOption.getAsString(); String birthdayString = birthdayOption.getAsString();
Date birthday = parseBirthday(birthdayString); Date birthday = parseBirthday(birthdayString);
if (birthday == null) { if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.birthday.command; package cc.fascinated.bat.features.birthday.command;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.UserBirthday; import cc.fascinated.bat.features.birthday.UserBirthday;
@ -10,12 +10,10 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService; import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -25,17 +23,17 @@ import org.springframework.stereotype.Component;
*/ */
@Component("birthday:view.sub") @Component("birthday:view.sub")
@CommandInfo(name = "view", description = "Add your birthday to this guild") @CommandInfo(name = "view", description = "Add your birthday to this guild")
public class ViewSubCommand extends BatCommand { public class ViewSubCommand extends BatSubCommand {
private final UserService userService; private final UserService userService;
@Autowired @Autowired
public ViewSubCommand(@NonNull UserService userService) { public ViewSubCommand(@NonNull UserService userService) {
this.userService = userService; this.userService = userService;
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the birthday of", false)); super.addOption(OptionType.USER, "user", "The user to view the birthday of", false);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
BirthdayProfile profile = guild.getBirthdayProfile(); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed() event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,6 +1,5 @@
package cc.fascinated.bat.features.birthday.profile; package cc.fascinated.bat.features.birthday.profile;
import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.common.Serializable; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.birthday.UserBirthday; import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
@ -158,7 +157,7 @@ public class BirthdayProfile extends Serializable {
return; return;
} }
TextChannel channel = ChannelUtils.getTextChannel(channelId); TextChannel channel = discordGuild.getTextChannelById(channelId);
if (channel == null) { // this should never happen if (channel == null) { // this should never happen
channelId = null; channelId = null;
return; return;

@ -1,81 +0,0 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.common.NumberUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
@Setter
public class CounterChannel {
/**
* The channel id of this counter channel
*/
private final String channelId;
/**
* The current count of this counter channel
*/
private int currentCount;
/**
* Whether the counter should reset when a user
* inputs the wrong number or a non number
*/
private boolean breakable;
/**
* Increment the counter
*
* @param input the input to increment the counter with
* @return the return type of the increment
*/
public CounterResult incrementChannel(String input) {
int number = NumberUtils.parseInt(input);
// The number is invalid
if (number == -1 || number != this.nextNumber()) {
// The counter was broken
if (this.isBreakable()) {
this.reset();
return CounterResult.BROKEN;
}
return CounterResult.INVALID_NUMBER;
}
this.currentCount++;
return CounterResult.SUCCESS;
}
/**
* Gets the next number for this counter
*/
public int nextNumber() {
return currentCount + 1;
}
/**
* Resets the current count
* <p>
* This usually happens if the counter is broken
* </p>
*/
public void reset() {
currentCount = 0;
}
/**
* Gets the channel
*
* @return the channel
*/
public TextChannel getChannel() {
return ChannelUtils.getTextChannel(channelId);
}
}

@ -1,23 +0,0 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.counter.command.CounterCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class CounterFeature extends Feature {
@Autowired
public CounterFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Counter", FeatureProfile.FeatureState.DISABLED, true);
super.registerCommand(commandService, context.getBean(CounterCommand.class));
}
}

@ -1,89 +0,0 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class CounterProfile extends Serializable {
/**
* The counter channels in this guild
*/
private final List<CounterChannel> counters = new ArrayList<>();
/**
* Gets the counter channel by the channel id
*
* @param channelId the id of the channel
* @return the counter channel
*/
public CounterChannel getCounterChannel(String channelId) {
for (CounterChannel counter : counters) {
if (counter.getChannelId().equals(channelId)) {
return counter;
}
}
return null;
}
/**
* Removes the channel from the counter channels
*
* @param channel the channel to remove
*/
public void removeChannel(TextChannel channel) {
CounterChannel counterChannel = getCounterChannel(channel.getId());
if (counterChannel != null) {
counters.remove(counterChannel);
}
}
/**
* Sets up the channel as a counter channel
*
* @param channel the channel to set as a counter channel
* @param breakable whether the counter should reset when a user inputs the wrong number or a non number
*/
public void setupChannel(TextChannel channel, boolean breakable) {
counters.add(new CounterChannel(channel.getId(), 0, breakable));
}
@Override
public void load(Document document, Gson gson) {
for (Document counterDocument : document.getList("counters", Document.class, new ArrayList<>())) {
counters.add(new CounterChannel(
counterDocument.getString("channelId"),
counterDocument.getInteger("currentCount"),
counterDocument.getBoolean("breakable")
));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
List<Document> counterDocuments = new ArrayList<>();
for (CounterChannel counter : counters) {
Document counterDocument = new Document();
counterDocument.append("channelId", counter.getChannelId());
counterDocument.append("currentCount", counter.getCurrentCount());
counterDocument.append("breakable", counter.isBreakable());
counterDocuments.add(counterDocument);
}
document.append("counters", counterDocuments);
return document;
}
@Override
public void reset() {
counters.clear();
}
}

Some files were not shown because too many files have changed in this diff Show More