Compare commits

..

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

208 changed files with 1552 additions and 6546 deletions

@ -16,4 +16,4 @@
# under the License.
wrapperVersion=3.3.2
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
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
WORKDIR /home/container
@ -11,7 +11,7 @@ COPY . .
RUN mvn package -q -Dmaven.test.skip -DskipTests -T2C
# 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
WORKDIR /home/container

42
pom.xml

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<version>3.3.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -41,7 +41,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<version>3.5.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
@ -75,11 +75,6 @@
<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>
@ -99,25 +94,25 @@
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.16.0</version>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-bom</artifactId>
<version>5.5.0</version>
<version>5.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-springboot-v3</artifactId>
<version>5.5.0</version>
<version>5.2.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId>
<version>5.5.0</version>
<version>5.2.4</version>
<scope>compile</scope>
</dependency>
@ -142,7 +137,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -154,13 +149,13 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
<version>2.10.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.1</version>
<version>5.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -172,23 +167,14 @@
<dependency>
<groupId>se.michaelthelin.spotify</groupId>
<artifactId>spotify-web-api-java</artifactId>
<version>8.4.1</version>
<version>8.4.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>xyz.mcutils</groupId>
<artifactId>mcutils-java-library</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.github.Steppschuh</groupId>
<artifactId>Java-Markdown-Generator</artifactId>
<version>1.3.2</version>
<groupId>uk.co.conoregan</groupId>
<artifactId>themoviedbapi</artifactId>
<version>2.1.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

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

@ -4,7 +4,7 @@ package cc.fascinated.bat;
* @author Fascinated (fascinated7)
*/
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 BOT_OWNER = "474221560031608833";
public static final String PRIVACY_POLICY_URL = "https://git.fascinated.cc/Fascinated/Bat/raw/branch/master/privacy-policy.txt";

@ -19,13 +19,25 @@ public class Emojis {
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 {
log.info("Loading emojis...");
JDA jda = DiscordService.JDA;
SPOTIFY_EMOJI = jda.getEmojiById("1256629771975266479");
CHECK_MARK_EMOJI = jda.getEmojiById("1258922852669853817");
CHECK_MARK_EMOJI = jda.getEmojiById("1256633734065557676");
CROSS_MARK_EMOJI = jda.getEmojiById("1256634487429922897");
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("");
PLAY_EMOJI = Emoji.fromUnicode("");
SKIP_EMOJI = Emoji.fromUnicode("");

@ -3,18 +3,17 @@ package cc.fascinated.bat.command;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.*;
import net.dv8tion.jda.api.EmbedBuilder;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
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.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 java.util.*;
@ -27,21 +26,18 @@ public abstract class BatCommand {
/**
* The info of this command.
*/
@NonNull
private final InternalCommandInfo info;
@NonNull private final InternalCommandInfo info;
/**
* The feature this command belongs to.
*/
@Setter
private Feature feature;
@Setter private Feature feature;
/**
* The snowflake of this command, set when
* this command is registered with Discord.
*/
@Setter
private long snowflake;
@Setter private long snowflake;
/**
* The sub commands of this command, if any.
@ -51,29 +47,27 @@ public abstract class BatCommand {
/**
* The internal data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private CommandDataImpl commandData;
@Setter(AccessLevel.PRIVATE) private CommandDataImpl commandData;
/**
* The internal subcommand data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private SubcommandData subcommandData;
@Setter(AccessLevel.PRIVATE) private SubcommandData subcommandData;
public BatCommand() {
if (!getClass().isAnnotationPresent(CommandInfo.class)) {
throw new IllegalStateException("Missing @CommandInfo annotation in " + getClass().getSimpleName());
}
info = new InternalCommandInfo(getClass().getAnnotation(CommandInfo.class));
List<IntegrationType> integrationTypes = new ArrayList<>(Collections.singletonList(IntegrationType.GUILD_INSTALL));
if (info.isUserInstall()) {
integrationTypes.add(IntegrationType.USER_INSTALL);
}
commandData = new CommandDataImpl(info.getName(), info.getDescription())
// .setDefaultPermissions(DefaultMemberPermissions.enabledFor(info.getPermissions()))
.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)
);
.setContexts(InteractionContextType.ALL)
.setIntegrationTypes(integrationTypes)
.setGuildOnly(info.isGuildOnly());
}
/**
@ -83,13 +77,9 @@ public abstract class BatCommand {
* @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
* @param event the event that invoked this command
*/
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) { }
/**
* Register the given sub commands.
@ -130,117 +120,4 @@ public abstract class BatCommand {
protected final void addOptions(OptionData... options) {
commandData.addOptions(options);
}
/**
* 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
}
}
}

@ -5,10 +5,6 @@ import lombok.Getter;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@ -20,7 +16,10 @@ public enum Category {
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"),
MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"),
MEDIA(Emoji.fromFormatted("U+1F3A5"), "Media"),
MUSIC(Emoji.fromFormatted("U+1F3B5"), "Music"),
MOVIES_TV(Emoji.fromFormatted("U+1F3A5"), "Movies & TV"),
MESSAGES(Emoji.fromFormatted("U+1F4A3"), "Messages"),
LOGS(Emoji.fromFormatted("U+1F4D1"), "Logs"),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber");
/**
@ -47,13 +46,4 @@ public enum Category {
}
return null;
}
/**
* Gets the categories sorted by their name
*
* @return the sorted categories
*/
public static List<Category> getSortedByName() {
return Arrays.stream(Category.values()).sorted(Comparator.comparing(Category::getName)).toList();
}
}

@ -44,13 +44,6 @@ public @interface CommandInfo {
*/
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
*

@ -48,19 +48,13 @@ public class InternalCommandInfo {
*/
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();
guildOnly = annotation.guildOnly();
userInstall = annotation.userInstall();
botOwnerOnly = annotation.botOwnerOnly();
prefixAllowed = annotation.prefixAllowed();
}
}

@ -19,7 +19,7 @@ public class ChannelUtils {
* @return the user with the given id
*/
private static TextChannel getTextChannel(String id, int retries) {
if (retries >= 10) {
if (retries >= 25) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}

@ -3,18 +3,12 @@ package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
@UtilityClass
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 SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withLocale(Locale.UK)
.withZone(ZONE_ID);
/**
* Gets the date from a string.
@ -25,14 +19,4 @@ public class DateUtils {
public static Date getDateFromString(String 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,13 +5,13 @@ import lombok.NonNull;
/**
* @author Fascinated (fascinated7)
*/
public class DescriptionBuilder {
public class EmbedDescriptionBuilder {
/**
* Where the description is stored
*/
private final StringBuilder builder = new StringBuilder();
public DescriptionBuilder(String title) {
public EmbedDescriptionBuilder(String title) {
if (title == null) {
return;
}
@ -19,19 +19,19 @@ public class DescriptionBuilder {
}
@NonNull
public DescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
public EmbedDescriptionBuilder appendLine(@NonNull String line, boolean arrow) {
builder.append(arrow ? "" : "").append(line).append("\n");
return this;
}
@NonNull
public DescriptionBuilder appendSubtitle(@NonNull String title) {
public EmbedDescriptionBuilder appendSubtitle(@NonNull String title) {
builder.append("**").append(title).append("**").append("\n");
return this;
}
@NonNull
public DescriptionBuilder emptyLine() {
public EmbedDescriptionBuilder emptyLine() {
builder.append("\n");
return this;
}

@ -48,26 +48,25 @@ public class EmbedUtils {
/**
* Builds a generic interaction error embed
*
* @param ex the exceptionk
* @param ex the exception
* @return the embed builder
*/
public static EmbedBuilder genericInteractionError(Exception ex) {
TextChannel channel = ChannelUtils.getTextChannel(Config.INSTANCE.getLogsChannel());
EmbedBuilder embed = errorEmbed()
if (channel != null) {
channel.sendMessageEmbeds(EmbedUtils.errorEmbed()
.setDescription("""
An error has occurred while processing %s interaction. If this issue persists, please contact the developers.
Cause: `%s`
An error has occurred while processing an interaction. Please check the logs for more information.
```java
%s
```""".formatted(
channel == null ? "an" : "your",
ex.getStackTrace()[0].getClassName(),
ex.getLocalizedMessage()
));
if (channel != null) {
channel.sendMessageEmbeds(embed.build()).queue();
```""".formatted(ex.getLocalizedMessage()))
.build()).queue();
}
return embed;
return EmbedUtils.errorEmbed()
.setDescription("""
An error has occurred while processing your interaction. Please check the logs for more information.
```java
%s
```""".formatted(ex.getLocalizedMessage()));
}
}

@ -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;
}
}

@ -24,39 +24,4 @@ public final class MathUtils {
).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;
}
}

@ -14,7 +14,7 @@ public class NumberFormatter {
* 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 DecimalFormat FORMAT = new DecimalFormat("#,##0.##");
private static final DecimalFormat FORMAT = new DecimalFormat("###.##");
/**
* Format the provided double
@ -47,8 +47,8 @@ public class NumberFormatter {
* @param input the value to format
* @return the formatted double, in the format of xx,xxx,xxx
*/
public static String simpleFormat(double input) {
return FORMAT.format(input);
public static String formatCommas(double 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;
}
}
}

@ -2,13 +2,9 @@ package cc.fascinated.bat.common;
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.Role;
import java.awt.*;
import java.util.EnumSet;
/**
* @author Fascinated (fascinated7)
*/
@ -25,60 +21,4 @@ public class RoleUtils {
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
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());
}
}

@ -37,18 +37,4 @@ public class StringUtils {
}
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);
}
}

@ -19,11 +19,11 @@ public class UserUtils {
* @return the user with the given id
*/
private static User getUser(String id, int retries) {
if (retries >= 10) {
if (retries >= 25) {
log.error("Failed to find user \"{}\" after {} retries.", id, retries);
return null;
}
User user = DiscordService.JDA.retrieveUserById(id).complete();
User user = DiscordService.JDA.getUserById(id);
if (user == null) {
return getUser(id, retries + 1);
}

@ -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);
}
}
}

@ -7,14 +7,9 @@ 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.ScoreSaberScoreToken;
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.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.ChannelDeleteEvent;
import net.dv8tion.jda.api.events.channel.update.*;
@ -32,7 +27,6 @@ import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateBoostTime
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.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.interaction.ModalInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
@ -42,14 +36,13 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
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.role.update.RoleUpdateColorEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdateIconEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent;
import net.dv8tion.jda.api.events.role.update.RoleUpdatePermissionsEvent;
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.UserUpdateNameEvent;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import java.time.OffsetDateTime;
import java.util.List;
@ -188,93 +181,6 @@ public interface EventListener {
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;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.service.CommandService;
import lombok.Getter;
import lombok.NonNull;
@ -19,16 +20,16 @@ public abstract class Feature {
*/
private final String name;
/**
* The default state of the feature
*/
private final FeatureProfile.FeatureState defaultState;
/**
* The description of the feature
*/
public final boolean canBeDisabled;
/**
* The category of the feature
*/
private final Category category;
/**
* Registers the command for the feature
*

@ -1,7 +1,7 @@
package cc.fascinated.bat.features.afk;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.afk.command.AfkCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
@Component
public class AfkFeature extends Feature {
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));
}

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.afk.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
@ -9,7 +8,6 @@ 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;
@ -21,7 +19,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@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 AfkCommand() {
super.addOptions(new OptionData(OptionType.STRING, "reason", "The reason for being AFK", false));
@ -34,12 +32,10 @@ public class AfkCommand extends BatCommand {
* @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
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);
String reason = null;
OptionMapping reasonOption = event.getOption("reason");

@ -1,7 +1,7 @@
package cc.fascinated.bat.features.autorole;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.autorole.command.AutoRoleCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
public class AutoRoleFeature extends Feature {
@Autowired
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));
}

@ -9,7 +9,6 @@ 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.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -31,7 +30,7 @@ public class AddSubCommand extends BatCommand {
}
@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);
// Check if the guild has reached the maximum auto roles count
int maxRoleSlots = AutoRoleProfile.getMaxRoleSlots(guild);
@ -44,7 +43,12 @@ public class AddSubCommand extends BatCommand {
}
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();
// Check if the role is already in the auto roles list

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

@ -8,7 +8,6 @@ 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;
@ -20,7 +19,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatCommand {
@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);
profile.reset();

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
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.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatCommand {
@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);
if (profile.getRoles().isEmpty()) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,7 +8,6 @@ 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.Role;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -30,10 +29,15 @@ public class RemoveSubCommand extends BatCommand {
}
@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);
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();
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,10 @@
package cc.fascinated.bat.features.base;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
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.botadmin.premium.PremiumAdminCommand;
import cc.fascinated.bat.features.base.commands.discord.LookupUserCommand;
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.general.*;
import cc.fascinated.bat.features.base.commands.general.avatar.AvatarCommand;
@ -14,8 +13,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.channel.ChannelCommand;
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 lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -29,10 +26,10 @@ import org.springframework.stereotype.Component;
public class BaseFeature extends Feature {
@Autowired
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(BotAdminCommand.class));
super.registerCommand(commandService, context.getBean(PremiumAdminCommand.class));
super.registerCommand(commandService, context.getBean(MemberCountCommand.class));
super.registerCommand(commandService, context.getBean(ChannelCommand.class));
super.registerCommand(commandService, context.getBean(VoteCommand.class));
@ -45,9 +42,6 @@ public class BaseFeature extends Feature {
super.registerCommand(commandService, context.getBean(ImageCommand.class));
super.registerCommand(commandService, context.getBean(FeatureCommand.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));
super.registerCommand(commandService, context.getBean(LookupUserCommand.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,23 @@
package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.command.BatCommand;
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)
public class PremiumAdminCommand extends BatCommand {
@Autowired
public PremiumAdminCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class)
);
}
}

@ -8,7 +8,6 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService;
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;
@ -21,18 +20,18 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premium-remove", description = "Remove premium from a guild")
public class PremiumRemoveSubCommand extends BatCommand {
@CommandInfo(name = "remove", description = "Remove premium from a guild")
public class RemoveSubCommand extends BatCommand {
private final GuildService guildService;
@Autowired
public PremiumRemoveSubCommand(GuildService guildService) {
public RemoveSubCommand(GuildService guildService) {
this.guildService = guildService;
super.addOptions(new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true));
}
@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");
if (guildOption == null) {
event.reply("Please provide a guild id").queue();

@ -8,7 +8,6 @@ import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService;
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;
@ -21,12 +20,12 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "premium-set", description = "Adds premium to a guild")
public class PremiumSetSubCommand extends BatCommand {
@CommandInfo(name = "set", description = "Adds premium to a guild")
public class SetSubCommand extends BatCommand {
private final GuildService guildService;
@Autowired
public PremiumSetSubCommand(@NonNull GuildService guildService) {
public SetSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService;
super.addOptions(
new OptionData(OptionType.STRING, "guild", "The guild id to set as premium", true),
@ -35,7 +34,7 @@ public class PremiumSetSubCommand extends BatCommand {
}
@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");
if (guildOption == null) {
event.reply("Please provide a guild id").queue();

@ -0,0 +1,81 @@
package cc.fascinated.bat.features.base.commands.discord;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.LongUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.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 = "lookupuser", description = "Lookup a user", userInstall = true)
public class LookupUserCommand extends BatCommand {
public LookupUserCommand() {
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, @NonNull SlashCommandInteraction event) {
OptionMapping idOption = event.getOption("id");
if (idOption == null) {
return;
}
String id = idOption.getAsString();
if (!LongUtils.isLong(id)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You need to provide a valid user id")
.build())
.setEphemeral(true)
.queue();
return;
}
DiscordService.JDA.retrieveUserById(id).queue(target -> {
if (target == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("User `%s` not found".formatted(id))
.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(flag.getName()).append(", ");
}
target.retrieveProfile().queue(profile -> event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("User Lookup")
.appendLine("Name: `%s`".formatted(target.getGlobalName()), 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,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,15 +1,14 @@
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.EmbedUtils;
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;
@ -19,13 +18,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "8ball", description = "Ask the magic 8ball a question",
guildOnly = false,
userInstall = true,
prefixAllowed = true,
category = Category.FUN
)
@CommandInfo(name = "8ball", description = "Ask the magic 8ball a question", guildOnly = false, userInstall = true)
public class EightBallCommand extends BatCommand {
private final String[] responses = new String[]{
"It is certain",
@ -55,18 +48,16 @@ public class EightBallCommand extends BatCommand {
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
String question = super.getArgument("question", arguments, 0, true, event).getAsString();
if (question == null) {
super.replyEmbed(commandMessage, event, EmbedUtils.errorEmbed()
.setDescription("You need to provide a question to ask the 8ball")
);
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping questionOption = event.getOption("question");
if (questionOption == null) {
return;
}
String question = questionOption.getAsString();
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))
);
.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();
}
}

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.thecatapi.CatImageToken;
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;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatCommand {
@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);
if (responseEntity == null || responseEntity.length == 0) {
event.reply("Failed to get a cat image!").queue();

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.dogceo.RandomImage;
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;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatCommand {
@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);
if (responseEntity == null) {
event.reply("Failed to get a dog image!").queue();

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomd.RandomDuck;
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;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "duck", description = "Get a random duck image")
public class DuckSubCommand extends BatCommand {
@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);
if (responseEntity == null) {
event.reply("Failed to get a duck image!").queue();

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.randomfox.RandomFoxToken;
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;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatCommand {
@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);
if (responseEntity == null) {
event.reply("Failed to get a fox image!").queue();

@ -1,9 +1,11 @@
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
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.BatUser;
import cc.fascinated.bat.service.DiscordService;
@ -12,7 +14,6 @@ import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.JDA;
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.beans.factory.annotation.Autowired;
@ -25,12 +26,11 @@ import java.lang.management.RuntimeMXBean;
* @author Fascinated (fascinated7)
*/
@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, userInstall = true)
public class BotStatsCommand extends BatCommand {
private final GuildService guildService;
private final UserService userService;
private final RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
private final Runtime runtime = Runtime.getRuntime();
@Autowired
public BotStatsCommand(@NonNull GuildService guildService, @NonNull UserService userService) {
@ -39,19 +39,17 @@ public class BotStatsCommand extends BatCommand {
}
@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;
long memoryUsed = (runtime.totalMemory() - runtime.freeMemory());
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
new DescriptionBuilder("Bat Statistics")
new EmbedDescriptionBuilder("Bat Statistics")
.appendLine("Guilds: **%s**".formatted(NumberFormatter.format(jda.getGuilds().size())), true)
.appendLine("Users: **%s**".formatted(NumberFormatter.format(jda.getUsers().size())), true)
.appendLine("Gateway Ping: **%sms**".formatted(jda.getGatewayPing()), true)
.emptyLine()
.appendSubtitle("Bot Statistics")
.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 Users: **%s**".formatted(NumberFormatter.format(userService.getUsers().size())), true)
.build()

@ -1,38 +1,38 @@
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.Emojis;
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.InteractionBuilder;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import 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.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.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "help",
description = "View the bots command categories",
userInstall = true,
category = Category.GENERAL
)
@CommandInfo(name = "help", description = "View the bots command categories.", guildOnly = false)
public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService;
@ -42,53 +42,62 @@ public class HelpCommand extends BatCommand implements EventListener {
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
InteractionBuilder interactionBuilder = new InteractionBuilder();
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;
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
event.replyEmbeds(createHomeEmbed()).addComponents(createHomeActions()).queue();
}
interactionBuilder.addStringSelect(
category.getName(),
"View commands in the %s category".formatted(category.getName()),
category.getEmoji(),
(buttonEvent) -> {
DescriptionBuilder description = new DescriptionBuilder(null);
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);
@Override
public void onStringSelectInteraction(BatGuild guild, @NonNull BatUser user, @NonNull StringSelectInteractionEvent event) {
String item = event.getSelectedOptions().get(0).getValue();
if (item.equalsIgnoreCase("home")) {
event.editMessageEmbeds(createHomeEmbed()).queue();
return;
}
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.reply("hello this doesnt work yet").queue();
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 +105,10 @@ public class HelpCommand extends BatCommand implements EventListener {
*
* @return The home embed
*/
private MessageEmbed createHomeEmbed(boolean ranInsideGuild) {
private MessageEmbed createHomeEmbed() {
StringBuilder categories = new StringBuilder();
for (Category category : Category.values()) {
if (commandService.getCommandsByCategory(category, ranInsideGuild).isEmpty()) {
continue;
}
long commandCount = commandService.getCommandsByCategory(category, ranInsideGuild).size();
long commandCount = commandService.getCommandsByCategory(category).size();
categories.append("➜ %s - **%s Command%s**\n".formatted(
category.getName(),
commandCount,
@ -112,16 +118,40 @@ public class HelpCommand extends BatCommand implements EventListener {
return EmbedUtils.genericEmbed()
.setDescription("""
**Welcome to the Bat Help Menu!**%s
**Welcome to the Bat Help Menu!**
%s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted(
!ranInsideGuild ? "\n*guild only commands won't be shown here*" : "",
categories.toString(),
Consts.TERMS_OF_SERVICE_URL,
Consts.PRIVACY_POLICY_URL
))
.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")));
for (Category category : Category.values()) {
options.add(SelectOption.of(category.getName(), category.getName()).withEmoji(category.getEmoji()));
}
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.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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;
@ -18,10 +16,10 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@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 {
@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()
.setDescription("You can invite the bot to your server by clicking [here](%s)".formatted(Consts.INVITE_URL))
.build())

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.DiscordService;
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;
@ -17,16 +15,10 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "ping",
description = "Gets the ping of the bot",
guildOnly = false,
userInstall = true,
category = Category.GENERAL
)
@CommandInfo(name = "ping", description = "Gets the ping of the bot", guildOnly = false, userInstall = true)
public class PingCommand extends BatCommand {
@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();
event.reply("Pinging...").queue(response -> {
response.editOriginal("Gateway response time: `%sms`\nAPI response time `%sms`".formatted(

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

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.base.commands.general.avatar;
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;
@ -12,7 +11,7 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 {
@Autowired
public AvatarCommand(@NonNull ApplicationContext context) {

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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 net.dv8tion.jda.api.utils.ImageProxy;
@ -18,11 +16,12 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 {
@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();
if (icon == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("**%s** does not have an avatar!".formatted(guild.getName()))

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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;
@ -21,18 +19,24 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the avatar of", true));
}
@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");
assert userOption != null;
User target = userOption.getAsUser();
if (userOption == null) {
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()
.setAuthor("%s's Avatar".formatted(target.getName()), null, target.getEffectiveAvatarUrl())
.setImage(target.getEffectiveAvatarUrl())

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.base.commands.general.banner;
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;
@ -12,7 +11,7 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 {
@Autowired
public BannerCommand(@NonNull ApplicationContext context) {

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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 net.dv8tion.jda.api.utils.ImageProxy;
@ -18,10 +16,10 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 {
@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();
if (banner == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -1,14 +1,12 @@
package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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;
@ -22,16 +20,23 @@ import org.springframework.stereotype.Component;
* @author Nick (okNick)
*/
@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 UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the banner of", true));
}
@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");
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();
ImageProxy banner = target.retrieveProfile().complete().getBanner();
if (banner == null) {

@ -1,5 +1,6 @@
package cc.fascinated.bat.features.base.commands.server;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
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.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.OnlineStatus;
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.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author Nick (okNick)
*/
@ -21,10 +25,14 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "membercount", description = "View the member count of the server!")
public class MemberCountCommand extends BatCommand {
@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();
int totalMembers = 0, totalUsers = 0, totalBots = 0;
Map<OnlineStatus, Integer> memberCounts = new HashMap<>();
for (Member guildMember : discordGuild.getMembers()) {
OnlineStatus status = guildMember.getOnlineStatus();
memberCounts.put(status, memberCounts.getOrDefault(status, 0) + 1);
if (guildMember.getUser().isBot()) {
totalBots++;
} else {
@ -38,10 +46,23 @@ public class MemberCountCommand extends BatCommand {
Total Members: `%s`
Total Users: `%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(totalUsers),
NumberFormatter.format(totalBots)
)).build()).queue();
NumberFormatter.format(totalBots),
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.Permission;
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;
@ -22,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "premium", description = "View the premium information for the guild", requiredPermissions = Permission.ADMINISTRATOR)
public class PremiumCommand extends BatCommand {
@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();
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
if (premium.hasPremium()) {

@ -8,7 +8,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
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.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -28,7 +27,7 @@ public class RemoveTopicSubCommand extends BatCommand {
}
@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();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,7 +8,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
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.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -33,7 +32,7 @@ public class SetTopicSubCommand extends BatCommand {
}
@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();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -7,7 +7,6 @@ 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.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -27,7 +26,7 @@ public class ViewTopicSubCommand extends BatCommand {
}
@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();
if (!(target instanceof TextChannel textChannel)) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -4,13 +4,12 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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.BatUser;
import cc.fascinated.bat.service.FeatureService;
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;
@ -31,7 +30,7 @@ public class DisableSubCommand extends BatCommand {
}
@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();
OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) {

@ -4,13 +4,12 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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.BatUser;
import cc.fascinated.bat.service.FeatureService;
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;
@ -31,7 +30,7 @@ public class EnableSubCommand extends BatCommand {
}
@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();
OptionMapping featureOption = event.getOption("feature");
if (featureOption == null) {

@ -4,13 +4,12 @@ import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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.BatUser;
import cc.fascinated.bat.service.FeatureService;
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;
@ -22,7 +21,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "list", description = "Lists the features and their states")
public class ListSubCommand extends BatCommand {
@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();
for (Feature feature : FeatureService.INSTANCE.getFeaturesSorted()) {
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,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.features.Feature;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.bson.Document;
import java.util.HashMap;
@ -16,6 +16,8 @@ import java.util.Map;
*/
@NoArgsConstructor
public class FeatureProfile extends Serializable {
private static final FeatureState DEFAULT_STATE = FeatureState.ENABLED;
/**
* The feature states
*/
@ -26,13 +28,13 @@ public class FeatureProfile extends Serializable {
*
* @return the feature states
*/
public FeatureState getFeatureState(@NonNull Feature feature) {
if (!feature.isCanBeDisabled()) {
return FeatureProfile.FeatureState.ENABLED;
public FeatureState getFeatureState(Feature feature) {
if (feature == null) {
return DEFAULT_STATE;
}
String featureName = feature.getName().toUpperCase();
if (!this.featureStates.containsKey(featureName)) {
this.featureStates.put(featureName, feature.getDefaultState());
this.featureStates.put(featureName, DEFAULT_STATE);
}
return this.featureStates.get(featureName);
}

@ -1,8 +1,8 @@
package cc.fascinated.bat.features.birthday;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.event.EventListener;
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.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
@ -23,7 +23,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private final 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;
registerCommand(commandService, context.getBean(BirthdayCommand.class));
@ -36,7 +36,7 @@ public class BirthdayFeature extends Feature implements EventListener {
private void checkBirthdays() {
for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled
if (batGuild == null) {
continue;
}
BirthdayProfile profile = batGuild.getBirthdayProfile();

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

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.birthday.command;
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;
@ -12,7 +11,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@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 {
@Autowired
public BirthdayCommand(@NonNull ApplicationContext context) {

@ -10,7 +10,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
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.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
@ -33,7 +32,7 @@ public class ChannelSubCommand extends BatCommand {
}
@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();
OptionMapping option = event.getOption("channel");
if (option == null) {

@ -9,7 +9,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
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;
@ -31,12 +30,17 @@ public class MessageSubCommand extends BatCommand {
}
@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();
OptionMapping messageOption = event.getOption("message");
assert messageOption != null;
String message = messageOption.getAsString();
if (messageOption == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You must provide a message")
.build()).queue();
return;
}
String message = messageOption.getAsString();
if (message.length() > 2000) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The message must be less than 2000 characters")

@ -9,7 +9,6 @@ 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;
@ -31,12 +30,17 @@ public class PrivateSubCommand extends BatCommand {
}
@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();
OptionMapping enabledOption = event.getOption("enabled");
assert enabledOption != null;
boolean enabled = enabledOption.getAsBoolean();
if (enabledOption == null) {
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());
if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -8,7 +8,6 @@ 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;
@ -21,7 +20,7 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "remove", description = "Remove your birthday from this guild")
public class RemoveSubCommand extends BatCommand {
@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();
profile.removeBirthday(user.getId());

@ -9,7 +9,6 @@ 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;
@ -37,7 +36,7 @@ public class SetSubCommand extends BatCommand {
}
@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();
if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed()
@ -47,9 +46,13 @@ public class SetSubCommand extends BatCommand {
}
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();
Date birthday = parseBirthday(birthdayString);
if (birthday == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -10,7 +10,6 @@ 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.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
@ -35,7 +34,7 @@ public class ViewSubCommand extends BatCommand {
}
@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();
if (!profile.hasChannelSetup()) {
event.replyEmbeds(EmbedUtils.errorEmbed()

@ -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();
}
}

@ -1,10 +0,0 @@
package cc.fascinated.bat.features.counter;
/**
* @author Fascinated (fascinated7)
*/
public enum CounterResult {
SUCCESS,
BROKEN,
INVALID_NUMBER,
}

@ -1,53 +0,0 @@
package cc.fascinated.bat.features.counter;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class CountingListener implements EventListener {
private final CounterFeature counterFeature;
@Autowired
public CountingListener(@NonNull CounterFeature counterFeature) {
this.counterFeature = counterFeature;
}
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
FeatureProfile featureProfile = guild.getFeatureProfile();
if (featureProfile.isFeatureDisabled(counterFeature)) {
return;
}
CounterProfile counterProfile = guild.getCounterProfile();
if (counterProfile == null) {
return;
}
CounterChannel counterChannel = counterProfile.getCounterChannel(event.getChannel().getId());
if (counterChannel == null) {
return;
}
CounterResult result = counterChannel.incrementChannel(event.getMessage().getContentRaw());
if (result == null) {
return;
}
switch (result) {
case SUCCESS -> event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
case INVALID_NUMBER -> event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
case BROKEN -> {
event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
event.getMessage().reply("%s, you have broken the counter!".formatted(event.getAuthor().getAsMention())).queue();
}
}
}
}

@ -1,26 +0,0 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "counter", description = "View or configure the counter settings", requiredPermissions = Permission.MANAGE_CHANNEL)
public class CounterCommand extends BatCommand {
@Autowired
public CounterCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetupSubCommand.class),
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class),
context.getBean(SetBreakingSubCommand.class)
);
}
}

@ -1,76 +0,0 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.ChannelType;
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.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.remove:sub")
@CommandInfo(name = "remove", description = "Remove a counter channel")
public class RemoveSubCommand extends BatCommand {
public RemoveSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
profile.removeChannel(textChannel);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("""
%s Successfully removed the counter channel %s
The count was `%s`
""".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
NumberFormatter.simpleFormat(counterChannel.getCurrentCount())
))
.build()).queue();
}
}

@ -1,84 +0,0 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.ChannelType;
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.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.setbreaking:sub")
@CommandInfo(name = "setbreaking", description = "Updates if the counter should break")
public class SetBreakingSubCommand extends BatCommand {
public SetBreakingSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
super.addOptions(new OptionData(OptionType.BOOLEAN, "breakable", "Should the channel break on a invalid or wrong number", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping breakableOption = event.getOption("breakable");
assert breakableOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
boolean breakable = breakableOption.getAsBoolean();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
counterChannel.setBreakable(breakable);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s The counter in %s will %s break on an invalid or wrong number".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
breakable ? "now" : "no longer"
))
.build()).queue();
if (channel.getId().equals(textChannel.getId())) { // Don't send in the same channel
return;
}
counterChannel.getChannel().sendMessage("The counter will %s break on an invalid or wrong number".formatted(
breakable ? "now" : "no longer"
)).queue();
}
}

@ -1,81 +0,0 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterChannel;
import cc.fascinated.bat.features.counter.CounterProfile;
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.ChannelType;
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.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.set:sub")
@CommandInfo(name = "set", description = "Set the current count in a channel")
public class SetSubCommand extends BatCommand {
public SetSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to set the count in", true));
super.addOptions(new OptionData(OptionType.INTEGER, "count", "The count to set", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping countOption = event.getOption("count");
assert countOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
int count = countOption.getAsInt();
CounterProfile profile = guild.getCounterProfile();
if (profile == null) {
return;
}
// Check if the channel is a counter channel
CounterChannel counterChannel = profile.getCounterChannel(textChannel.getId());
if (counterChannel == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel %s is not a counter channel".formatted(
Emojis.CROSS_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
return;
}
counterChannel.setCurrentCount(count);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s Successfully set the count in %s to %d".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention(),
count
))
.build()).queue();
counterChannel.getChannel().sendMessage("The count has been set to `%s`".formatted(
count
)).queue();
}
}

@ -1,67 +0,0 @@
package cc.fascinated.bat.features.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.counter.CounterProfile;
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.ChannelType;
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.unions.GuildChannelUnion;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component("counter.setup:sub")
@CommandInfo(name = "setup", description = "Setup a counter for a channel")
public class SetupSubCommand extends BatCommand {
public SetupSubCommand() {
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The channel to setup the counter in", true));
super.addOptions(new OptionData(OptionType.BOOLEAN, "breakable", "Should the channel break on a invalid or wrong number", true));
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, Message commandMessage, String[] arguments, SlashCommandInteraction event) {
OptionMapping channelOption = event.getOption("channel");
assert channelOption != null;
OptionMapping breakableOption = event.getOption("breakable");
assert breakableOption != null;
GuildChannelUnion targetChannel = channelOption.getAsChannel();
if (targetChannel.getType() != ChannelType.TEXT) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s The channel must be a text channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
TextChannel textChannel = targetChannel.asTextChannel();
CounterProfile profile = guild.getCounterProfile();
if (profile.getCounterChannel(textChannel.getId()) != null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("%s This channel is already a counter channel".formatted(Emojis.CROSS_MARK_EMOJI))
.build()).queue();
return;
}
boolean breakable = breakableOption.getAsBoolean();
profile.setupChannel(textChannel, breakable);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s Successfully setup the counter in %s".formatted(
Emojis.CHECK_MARK_EMOJI,
textChannel.getAsMention()
))
.build()).queue();
}
}

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

@ -1,7 +1,6 @@
package cc.fascinated.bat.features.drag.command;
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;
@ -12,7 +11,7 @@ import org.springframework.stereotype.Component;
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "drag", description = "Drag command", category = Category.GENERAL)
@CommandInfo(name = "drag", description = "Drag command")
public class DragCommand extends BatCommand {
@Autowired
public DragCommand(@NonNull ApplicationContext context) {

@ -11,7 +11,6 @@ import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@ -65,7 +64,7 @@ public class RequestSubCommand extends BatCommand implements EventListener {
}
@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) {
GuildVoiceState voiceState = member.getVoiceState();
// Check if the user is in a voice channel
if (voiceState == null || voiceState.getChannel() == null) {
@ -78,7 +77,7 @@ public class RequestSubCommand extends BatCommand implements EventListener {
}
OptionMapping userOption = event.getOption("user");
assert userOption != null;
if (userOption == null) return;
// Check if the user is in a voice channel
Member target = userOption.getAsMember();

@ -20,9 +20,6 @@ import java.util.Optional;
public class RequestListener implements EventListener {
@Override
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
if (guild == null) {
return;
}
if (!event.getComponentId().equals("drag-request-cancel")) {
return;
}

@ -19,9 +19,6 @@ import org.springframework.stereotype.Component;
public class TargetChannelListener implements EventListener {
@Override
public void onButtonInteraction(BatGuild guild, @NonNull BatUser user, @NonNull ButtonInteractionEvent event) {
if (guild == null) {
return;
}
User buttonUser = event.getUser();
Member member = guild.getDiscordGuild().getMember(buttonUser);
if (member == null) return;

@ -1,52 +0,0 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.leveling.command.LevelingCommand;
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;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class LevelingFeature extends Feature {
/**
* The cache of the required XP for each level.
*/
public static final Map<Integer, Double> xpRequiredCache = new HashMap<>();
@Autowired
public LevelingFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Leveling", FeatureProfile.FeatureState.DISABLED, true);
super.registerCommand(commandService, context.getBean(LevelingCommand.class));
}
/**
* Gets the amount of XP needed to level up.
*
* @param level The level.
* @param currentXp The current XP.
* @return The needed XP.
*/
public static double getNeededXP(int level, double currentXp) {
return xpRequiredCache.computeIfAbsent(level, integer -> getBaseXP(level) - currentXp);
}
/**
* Gets the base XP for a level.
*
* @param level The level.
* @return The base XP.
*/
public static double getBaseXP(int level) {
return 5 * (Math.pow(level, 2)) + (50 * level) + 100;
}
}

@ -1,54 +0,0 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.event.EventListener;
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.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@Log4j2(topic = "Leveling Listener")
public class LevelingListener implements EventListener {
private final LevelingFeature levelingFeature;
@Autowired
public LevelingListener(@NonNull LevelingFeature levelingFeature) {
this.levelingFeature = levelingFeature;
}
@Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
if (guild.getFeatureProfile().isFeatureDisabled(levelingFeature)) { // The feature is disabled
return;
}
LevelingProfile profile = guild.getLevelingProfile();
if (profile == null) {
return;
}
UserLevel userLevel = profile.getUserLevel(user.getId());
userLevel.addXp((int) MathUtils.random(15, 25));
if (!userLevel.canLevelUp()) {
return;
}
userLevel.levelup();
TextChannel notificationChannel = profile.getNotificationChannel();
if (notificationChannel == null) {
return;
}
notificationChannel.sendMessage("Congratulations %s, you have leveled up to `%s`! :tada:".formatted(
event.getAuthor().getAsMention(),
userLevel.getLevel()
)).queue();
}
}

@ -1,76 +0,0 @@
package cc.fascinated.bat.features.leveling;
import cc.fascinated.bat.common.ChannelUtils;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Setter
public class LevelingProfile extends Serializable {
/**
* The levels of each user.
*/
private final Map<String, UserLevel> userLevels = new HashMap<>();
/**
* The id of the level up notification channel.
*/
private String notificationChannelId;
/**
* Gets the user level of a user.
*
* @param userId The user ID.
* @return The user level.
*/
public UserLevel getUserLevel(String userId) {
return userLevels.computeIfAbsent(userId, s -> new UserLevel(1, 0, -1));
}
/**
* Gets the notification channel.
*
* @return The notification channel.
*/
public TextChannel getNotificationChannel() {
return ChannelUtils.getTextChannel(notificationChannelId);
}
@Override
public void load(Document document, Gson gson) {
for (String userId : document.keySet()) {
Document userDocument = document.get(userId, Document.class);
userLevels.put(userId, new UserLevel(
userDocument.getInteger("level"),
userDocument.getDouble("currentXp"),
(long) userDocument.getOrDefault("lastXpTimestamp", (long) -1)
));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (Map.Entry<String, UserLevel> entry : userLevels.entrySet()) {
document.put(entry.getKey(), new Document()
.append("level", entry.getValue().getLevel())
.append("currentXp", entry.getValue().getCurrentXp())
.append("lastXpTimestamp", entry.getValue().getLastXpTimestamp())
);
}
return document;
}
@Override
public void reset() {
userLevels.clear();
}
}

@ -1,68 +0,0 @@
package cc.fascinated.bat.features.leveling;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public class UserLevel {
/**
* The current level of the user.
*/
private int level;
/**
* The current XP of the user.
*/
private double currentXp;
/**
* The timestamp of the last message sent by the user.
*/
private long lastXpTimestamp;
/**
* Adds XP to the user.
*/
public void addXp(double xp) {
this.currentXp += xp;
}
/**
* Checks if the user can level up.
*
* @return If the user can level up.
*/
public boolean canLevelUp() {
// Handle XP cooldown
long cooldown = System.currentTimeMillis() - this.lastXpTimestamp;
if (cooldown < 30_000) {
return false;
}
this.lastXpTimestamp = System.currentTimeMillis();
return this.currentXp >= LevelingFeature.getNeededXP(this.level + 1, this.currentXp);
}
/**
* Levels up the user.
*/
public void levelup() {
do {
if (this.currentXp < LevelingFeature.getNeededXP(this.level + 1, this.currentXp)) {
break;
}
this.level++;
} while (this.currentXp >= LevelingFeature.getNeededXP(this.level + 1, this.currentXp));
}
/**
* Resets the user's level and XP.
*/
public void reset() {
this.level = 1;
this.currentXp = 0;
}
}

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