Compare commits

..

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

271 changed files with 5498 additions and 7996 deletions

View File

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

View File

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

69
pom.xml
View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</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>
@ -70,12 +70,6 @@
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<!-- Spring -->
@ -94,7 +88,24 @@
<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.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-springboot-v3</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongodb-springdata-v4-driver</artifactId>
<version>5.2.4</version>
</dependency>
<!-- Redis for caching -->
@ -118,42 +129,52 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<!-- Dependencies -->
<dependency>
<groupId>io.github.freya022</groupId>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>2ed819ad15</version>
<scope>compile</scope>
<version>5.0.0-beta.24</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
<scope>compile</scope>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.1</version>
<scope>compile</scope>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
<groupId>se.michaelthelin.spotify</groupId>
<artifactId>spotify-web-api-java</artifactId>
<version>8.4.0</version>
</dependency>
<dependency>
<groupId>com.github.Steppschuh</groupId>
<artifactId>Java-Markdown-Generator</artifactId>
<version>1.3.2</version>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>uk.co.conoregan</groupId>
<artifactId>themoviedbapi</artifactId>
<version>2.1.1</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import net.dv8tion.jda.api.entities.emoji.Emoji;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2(topic = "Emojis")
@Log4j2
public class Emojis {
public static final Emoji SPOTIFY_EMOJI;
public static final Emoji CHECK_MARK_EMOJI;
@ -17,19 +17,29 @@ public class Emojis {
public static final Emoji PAUSE_EMOJI;
public static final Emoji PLAY_EMOJI;
public static final Emoji SKIP_EMOJI;
public static final Emoji HOME_EMOJI;
/**
* Presence Status Emojis
*/
public static final Emoji ONLINE_EMOJI;
public static final Emoji IDLE_EMOJI;
public static final Emoji DND_EMOJI;
public static final Emoji OFFLINE_EMOJI;
static {
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("");
HOME_EMOJI = Emoji.fromUnicode("🏠");
log.info("Loaded emojis!");
}
}

View File

@ -1,31 +0,0 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import org.springframework.context.ApplicationContext;
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
)
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)
);
}
}

View File

@ -1,113 +0,0 @@
package cc.fascinated.bat.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}
});
}
}

View File

@ -1,55 +0,0 @@
package cc.fascinated.bat.base;
import cc.fascinated.bat.base.commands.fun.HowGayCommand;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.base.commands.botadmin.BotAdminCommand;
import cc.fascinated.bat.base.commands.fun.CoinFlipCommand;
import cc.fascinated.bat.base.commands.fun.EightBallCommand;
import cc.fascinated.bat.base.commands.fun.PPSizeCommand;
import cc.fascinated.bat.base.commands.fun.image.ImageCommand;
import cc.fascinated.bat.base.commands.general.*;
import cc.fascinated.bat.base.commands.general.avatar.AvatarCommand;
import cc.fascinated.bat.base.commands.general.banner.BannerCommand;
import cc.fascinated.bat.base.commands.server.MemberCountCommand;
import cc.fascinated.bat.base.commands.server.PremiumCommand;
import cc.fascinated.bat.base.commands.server.channel.ChannelCommand;
import cc.fascinated.bat.base.commands.server.feature.FeatureCommand;
import cc.fascinated.bat.base.commands.utility.PastebinCommand;
import cc.fascinated.bat.base.commands.utility.lookup.LookupCommand;
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 BaseFeature extends Feature {
@Autowired
public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Base", FeatureProfile.FeatureState.ENABLED, false);
super.registerCommand(commandService, context.getBean(PremiumCommand.class));
super.registerCommand(commandService, context.getBean(BotAdminCommand.class));
super.registerCommand(commandService, context.getBean(MemberCountCommand.class));
super.registerCommand(commandService, context.getBean(ChannelCommand.class));
super.registerCommand(commandService, context.getBean(VoteCommand.class));
super.registerCommand(commandService, context.getBean(PingCommand.class));
super.registerCommand(commandService, context.getBean(InviteCommand.class));
super.registerCommand(commandService, context.getBean(HelpCommand.class));
super.registerCommand(commandService, context.getBean(BotStatsCommand.class));
super.registerCommand(commandService, context.getBean(BannerCommand.class));
super.registerCommand(commandService, context.getBean(AvatarCommand.class));
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(HowGayCommand.class));
}
}

View File

@ -1,25 +0,0 @@
package cc.fascinated.bat.base.commands.botadmin;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.base.commands.botadmin.premium.PremiumRemoveSubCommand;
import cc.fascinated.bat.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)
);
}
}

View File

@ -1,40 +0,0 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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";
}
}

View File

@ -1,52 +0,0 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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 = "howgay",
description = "Checks how gay someone is",
userInstall = true,
category = Category.FUN
)
public class HowGayCommand extends BatCommand {
public HowGayCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user you want to check the gayness 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();
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new DescriptionBuilder("How Gay?")
.appendLine("%s is %s gay".formatted(
target.getAsMention(),
(int) MathUtils.random(0, 100)
), false)
.build())
.build()).queue();
}
}

View File

@ -1,57 +0,0 @@
package cc.fascinated.bat.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

@ -1,26 +0,0 @@
package cc.fascinated.bat.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.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 = "image", description = "View a random image", guildOnly = false, userInstall = true, category = Category.FUN)
public class ImageCommand extends BatCommand {
@Autowired
public ImageCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(CatSubCommand.class),
context.getBean(DogSubCommand.class),
context.getBean(FoxSubCommand.class),
context.getBean(DuckSubCommand.class)
);
}
}

View File

@ -1,132 +0,0 @@
package cc.fascinated.bat.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.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.common.model.BatGuild;
import cc.fascinated.bat.common.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.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "help",
description = "View the bots command categories",
userInstall = true,
category = Category.GENERAL
)
public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService;
@Autowired
public HelpCommand(@NonNull CommandService commandService) {
this.commandService = commandService;
}
@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;
}
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);
}
buttonEvent.editMessageEmbeds(EmbedUtils.genericEmbed()
.setDescription(description.build())
.build()).queue();
});
}
event.replyEmbeds(createHomeEmbed(event.isFromGuild()))
.addComponents(interactionBuilder.build())
.setEphemeral(true)
.queue();
}
/**
* Creates the home embed for the help command
*
* @return The home embed
*/
private MessageEmbed createHomeEmbed(boolean ranInsideGuild) {
StringBuilder categories = new StringBuilder();
for (Category category : Category.values()) {
if (commandService.getCommandsByCategory(category, ranInsideGuild).isEmpty()) {
continue;
}
long commandCount = commandService.getCommandsByCategory(category, ranInsideGuild).size();
categories.append("- %s: **%s Command%s**\n".formatted(
category.getName(),
commandCount,
commandCount == 1 ? "" : "s"
));
}
return EmbedUtils.genericEmbed()
.setDescription("""
**Welcome to the Bat Help Menu!**
Bat is a multi-purpose bot that has a variety of features to help you with your server. You can change the category be clicking on the category name in the list below.
%s%s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted(
categories.toString(),
!ranInsideGuild ? "\n*guild only commands won't be shown here*" : "",
Consts.TERMS_OF_SERVICE_URL,
Consts.PRIVACY_POLICY_URL
))
.build();
}
}

View File

@ -1,47 +0,0 @@
package cc.fascinated.bat.base.commands.server;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.NonNull;
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;
/**
* @author Nick (okNick)
*/
@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) {
Guild discordGuild = guild.getDiscordGuild();
int totalMembers = 0, totalUsers = 0, totalBots = 0;
for (Member guildMember : discordGuild.getMembers()) {
if (guildMember.getUser().isBot()) {
totalBots++;
} else {
totalUsers++;
}
totalMembers++;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("""
**Member Count**
Total Members: `%s`
Total Users: `%s`
Total Bots: `%s`
""".formatted(
NumberFormatter.format(totalMembers),
NumberFormatter.format(totalUsers),
NumberFormatter.format(totalBots)
)).build()).queue();
}
}

View File

@ -1,54 +0,0 @@
package cc.fascinated.bat.base.commands.utility;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

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

View File

@ -1,75 +0,0 @@
package cc.fascinated.bat.base.commands.utility.lookup;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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());
}
}

View File

@ -1,28 +0,0 @@
package cc.fascinated.bat.birthday.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.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 = "birthday", description = "Modify your birthday settings.", category = Category.UTILITY)
public class BirthdayCommand extends BatCommand {
@Autowired
public BirthdayCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(SetSubCommand.class),
context.getBean(RemoveSubCommand.class),
context.getBean(ChannelSubCommand.class),
context.getBean(MessageSubCommand.class),
context.getBean(ViewSubCommand.class),
context.getBean(PrivateSubCommand.class)
);
}
}

View File

@ -0,0 +1,97 @@
package cc.fascinated.bat.command;
import cc.fascinated.bat.features.Feature;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
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.internal.interactions.CommandDataImpl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
public abstract class BatCommand implements BatCommandExecutor {
/**
* The information about the command
*/
private final CommandInfo commandInfo;
/**
* The command data for the slash command
*/
private final CommandDataImpl commandData;
/**
* The sub commands of the command
*/
private final Map<String, BatSubCommand> subCommands = new HashMap<>();
/**
* The category of the command
*/
private Category category;
/**
* The feature that the command belongs to
*/
private Feature feature;
/**
* Whether the command can only be used by the bot owner
*/
private boolean botOwnerOnly;
/**
* The command snowflake from Discord
*/
private long commandSnowflake;
public BatCommand() {
this.commandInfo = getClass().getAnnotation(CommandInfo.class);
this.category = this.commandInfo.category();
this.botOwnerOnly = this.commandInfo.botOwnerOnly();
this.commandData = new CommandDataImpl(this.commandInfo.name(), this.commandInfo.description())
.setGuildOnly(this.commandInfo.guildOnly());
}
/**
* Adds a sub command to the command
*
* @param subCommand The sub command
*/
public void addSubCommand(@NonNull BatSubCommand subCommand) {
this.subCommands.put(subCommand.getCommandInfo().name().toLowerCase(), subCommand);
this.commandData.addSubcommands(subCommand.getCommandData());
}
/**
* Adds an option to the sub command
*
* @param optionType the type of the option
* @param name the name of the option
* @param description the description of the option
* @param required whether the option is required
*/
protected void addOption(OptionType optionType, String name, String description, boolean required) {
this.commandData.addOption(optionType, name, description, required);
}
/**
* Gets all the options for the command
*
* @param interaction The slash command interaction
* @return The option strings
*/
public List<String> getOptions(SlashCommandInteraction interaction) {
return interaction.getOptions().stream().map(OptionMapping::getName).toList();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,33 +5,30 @@ 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) {
if (title == null) {
return;
}
public EmbedDescriptionBuilder(String title) {
builder.append("**").append(title).append("**").append("\n");
}
@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;
}

View File

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

View File

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

View File

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

View File

@ -1,105 +0,0 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.service.InteractionService;
import lombok.Getter;
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.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* @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;
}
}

View File

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

View File

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

View File

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

View File

@ -1,20 +1,17 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
import java.text.DecimalFormat;
import java.util.Locale;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
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 +44,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);
}
/**

View File

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

View File

@ -1,9 +1,8 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.common.model.token.paste.PasteUploadToken;
import cc.fascinated.bat.model.token.paste.PasteUploadToken;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import java.net.URI;
@ -14,11 +13,10 @@ import java.net.http.HttpResponse;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
@Log4j2(topic = "PasteUtils")
@Log4j2
public class PasteUtils {
private static final String PASTE_URL = "https://paste.fascinated.cc/";
private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload?expires=" + 60 * 60 * 24 * 30; // 30 days
private static final String PASTE_UPLOAD_URL = PASTE_URL + "api/upload";
private static final HttpClient httpClient = HttpClient.newHttpClient();
/**

View File

@ -1,18 +1,12 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.common.model.BatGuild;
import lombok.experimental.UtilityClass;
import net.dv8tion.jda.api.Permission;
import cc.fascinated.bat.model.BatGuild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import java.awt.*;
import java.util.EnumSet;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class RoleUtils {
/**
* Checks if a member has permission to give the role to another member
@ -25,60 +19,4 @@ public class RoleUtils {
public static boolean hasPermissionToGiveRole(BatGuild guild, Member member, Role role) {
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());
}
}

View File

@ -0,0 +1,69 @@
package cc.fascinated.bat.common;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.SpotifyService;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import se.michaelthelin.spotify.model_objects.miscellaneous.CurrentlyPlaying;
import se.michaelthelin.spotify.model_objects.specification.Track;
/**
* @author Fascinated (fascinated7)
*/
@Log4j2
public class SpotifyUtils {
/**
* Gets the URL of the track that is currently playing.
*
* @param currentlyPlaying The currently playing object.
* @return The URL of the track that is currently playing.
*/
public static String getTrackUrl(CurrentlyPlaying currentlyPlaying) {
return "https://open.spotify.com/track/" + currentlyPlaying.getItem().getId();
}
/**
* Gets the formatted time of the currently playing track
*
* @param currentlyPlaying the currently playing track
* @return the formatted time
*/
public static String getFormattedTime(@NonNull CurrentlyPlaying currentlyPlaying) {
Track track = (Track) currentlyPlaying.getItem();
int currentMinutes = currentlyPlaying.getProgress_ms() / 1000 / 60;
int currentSeconds = currentlyPlaying.getProgress_ms() / 1000 % 60;
int totalMinutes = track.getDurationMs() / 1000 / 60;
int totalSeconds = track.getDurationMs() / 1000 % 60;
return "`%02d:%02d`/`%02d:%02d`".formatted(currentMinutes, currentSeconds, totalMinutes, totalSeconds);
}
/**
* Get the next track that is playing
*
* @param user The user to get the track for
* @param oldName The name of the old track
* @return The new track
*/
public static CurrentlyPlaying getNewTrack(@NonNull SpotifyService spotifyService, @NonNull BatUser user, @NonNull String oldName) {
int checks = 0;
try {
Thread.sleep(150);
while (checks < 10) {
CurrentlyPlaying currentlyPlaying = spotifyService.getCurrentlyPlayingTrack(user);
Track track = (Track) currentlyPlaying.getItem();
if (track.getName().equals(oldName)) {
Thread.sleep(250);
checks++;
} else {
log.info("Found new track \"{}\" in {} check{}", track.getName(), checks, checks == 1 ? "" : "s");
return currentlyPlaying;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,11 +1,8 @@
package cc.fascinated.bat.common;
import lombok.experimental.UtilityClass;
/**
* @author Fascinated (fascinated7)
*/
@UtilityClass
public class StringUtils {
/**
* Generates a random string
@ -37,18 +34,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);
}
}

View File

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

View File

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

View File

@ -1,248 +0,0 @@
package cc.fascinated.bat.common.command;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import lombok.*;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
/**
* @author Braydon
*/
@Getter
public abstract class BatCommand {
/**
* The info of this command.
*/
@NonNull
private final InternalCommandInfo info;
/**
* The feature this command belongs to.
*/
@Setter
private Feature feature;
/**
* The snowflake of this command, set when
* this command is registered with Discord.
*/
@Setter
private long snowflake;
/**
* The sub commands of this command, if any.
*/
private final Map<String, BatCommand> subCommands = Collections.synchronizedMap(new HashMap<>());
/**
* The internal data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private CommandDataImpl commandData;
/**
* The internal subcommand data for this command.
*/
@Setter(AccessLevel.PRIVATE)
private SubcommandData subcommandData;
public BatCommand() {
if (!getClass().isAnnotationPresent(CommandInfo.class)) {
throw new IllegalStateException("Missing @CommandInfo annotation in " + getClass().getSimpleName());
}
info = new InternalCommandInfo(getClass().getAnnotation(CommandInfo.class));
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)
);
}
/**
* Fired when this command is executed.
*
* @param guild the guild the command was executed in, if any
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage the message that invoked this command, if any
* @param arguments the arguments of the command, if any
* @param event the event that invoked this command, if any
*/
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member,
Message commandMessage, String[] arguments, SlashCommandInteraction event) {
}
/**
* Register the given sub commands.
*
* @param commands the commands to register
*/
protected final void addSubCommands(@NonNull BatCommand... commands) {
for (BatCommand command : commands) {
// Copy info from the parent command
if (command.getInfo().getCategory() != info.getCategory()) {
command.getInfo().setCategory(info.getCategory());
}
if (command.getInfo().getPermissions().length == 0) {
command.getInfo().setPermissions(info.getPermissions());
}
if (command.getInfo().isGuildOnly() != info.isGuildOnly()) {
command.getInfo().setGuildOnly(info.isGuildOnly());
}
if (command.getInfo().isBotOwnerOnly() != info.isBotOwnerOnly()) {
command.getInfo().setBotOwnerOnly(info.isBotOwnerOnly());
}
command.setSubcommandData(new SubcommandData(command.getInfo().getName(), command.getInfo().getDescription()));
for (OptionData option : command.getCommandData().getOptions()) {
command.getSubcommandData().addOptions(option);
}
commandData.addSubcommands(command.getSubcommandData());
subCommands.put(command.getInfo().getName(), command);
}
}
/**
* Add the given options
* to this command.
*
* @param options the options to add
*/
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
}
}
}

View File

@ -1,59 +0,0 @@
package cc.fascinated.bat.common.command;
import lombok.AllArgsConstructor;
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)
*/
@AllArgsConstructor
@Getter
public enum Category {
GENERAL(Emoji.fromUnicode("U+2699"), "General"),
FUN(Emoji.fromFormatted("U+1F973"), "Fun"),
SERVER(Emoji.fromFormatted("U+1F5A5"), "Server"),
MODERATION(Emoji.fromFormatted("U+1F6E0"), "Moderation"),
UTILITY(Emoji.fromFormatted("U+1F6E0"), "Utility"),
MEDIA(Emoji.fromFormatted("U+1F3A5"), "Media"),
BEAT_SABER(Emoji.fromFormatted("U+1FA84"), "Beat Saber");
/**
* The emoji for the category
*/
@NonNull private final Emoji emoji;
/**
* The name of the category
*/
@NonNull private final String name;
/**
* Gets a category by its name
*
* @param name the name of the category
* @return the category
*/
public static Category getByName(String name) {
for (Category category : Category.values()) {
if (category.getName().equalsIgnoreCase(name)) {
return 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();
}
}

View File

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

View File

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

View File

@ -0,0 +1,33 @@
package cc.fascinated.bat.controller;
import cc.fascinated.bat.service.SpotifyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Fascinated (fascinated7)
*/
@RestController
@RequestMapping(value = "/spotify")
public class SpotifyController {
private final SpotifyService spotifyService;
@Autowired
public SpotifyController(SpotifyService spotifyService) {
this.spotifyService = spotifyService;
}
/**
* A GET request to authorize the user with Spotify.
*
* @return the response entity
*/
@GetMapping(value = "/callback")
public ResponseEntity<String> authorizationCallback(@RequestParam(required = false) String code) {
return ResponseEntity.ok(spotifyService.authorize(code));
}
}

View File

@ -1,81 +0,0 @@
package cc.fascinated.bat.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);
}
}

View File

@ -1,23 +0,0 @@
package cc.fascinated.bat.counter;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.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));
}
}

View File

@ -1,89 +0,0 @@
package cc.fascinated.bat.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();
}
}

View File

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

View File

@ -1,53 +0,0 @@
package cc.fascinated.bat.counter;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}
}
}

View File

@ -1,26 +0,0 @@
package cc.fascinated.bat.counter.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.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)
);
}
}

View File

@ -1,76 +0,0 @@
package cc.fascinated.bat.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.counter.CounterChannel;
import cc.fascinated.bat.counter.CounterProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

@ -1,84 +0,0 @@
package cc.fascinated.bat.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.counter.CounterChannel;
import cc.fascinated.bat.counter.CounterProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

@ -1,81 +0,0 @@
package cc.fascinated.bat.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.counter.CounterChannel;
import cc.fascinated.bat.counter.CounterProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

@ -1,67 +0,0 @@
package cc.fascinated.bat.counter.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.counter.CounterProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.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();
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package cc.fascinated.bat.common.feature;
package cc.fascinated.bat.features;
import cc.fascinated.bat.common.command.BatCommand;
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
*
@ -36,10 +37,11 @@ public abstract class Feature {
* @param command The command to register
*/
public void registerCommand(@NonNull CommandService commandService, @NonNull BatCommand command) {
command.setFeature(this);
for (BatCommand subCommand : command.getSubCommands().values()) {
subCommand.setFeature(this);
// If the command using the default category then set the category to the feature's category
if (command.getCategory() == Category.GENERAL) {
command.setCategory(this.category);
}
command.setFeature(this);
commandService.registerCommand(command);
}
}

View File

@ -1,8 +1,8 @@
package cc.fascinated.bat.afk;
package cc.fascinated.bat.features.afk;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.afk.command.AfkCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.afk.command.AfkCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import org.springframework.context.ApplicationContext;
@ -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));
}

View File

@ -1,9 +1,9 @@
package cc.fascinated.bat.afk;
package cc.fascinated.bat.features.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;

View File

@ -1,9 +1,9 @@
package cc.fascinated.bat.afk;
package cc.fascinated.bat.features.afk;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.stereotype.Component;

View File

@ -1,45 +1,31 @@
package cc.fascinated.bat.afk.command;
package cc.fascinated.bat.features.afk.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.afk.profile.AfkProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.afk.profile.AfkProfile;
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 = "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));
super.addOption(OptionType.STRING, "reason", "The reason for being AFK", false);
}
/**
* Fired when this command is executed.
*
* @param guild the guild the command was executed in, if any
* @param user the user who executed the command
* @param channel the channel the command was executed in
* @param member the member who executed the command, null if not a guild
* @param commandMessage
* @param arguments
* @param event the event that invoked this command
*/
@Override
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");

View File

@ -1,7 +1,7 @@
package cc.fascinated.bat.afk.profile;
package cc.fascinated.bat.features.afk.profile;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatGuild;
import com.google.gson.Gson;
import lombok.NoArgsConstructor;
import net.dv8tion.jda.api.entities.Guild;

View File

@ -1,8 +1,8 @@
package cc.fascinated.bat.autorole;
package cc.fascinated.bat.features.autorole;
import cc.fascinated.bat.common.feature.Feature;
import cc.fascinated.bat.common.feature.FeatureProfile;
import cc.fascinated.bat.autorole.command.AutoRoleCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.autorole.command.AutoRoleCommand;
import cc.fascinated.bat.service.CommandService;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
@ -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));
}

View File

@ -1,9 +1,9 @@
package cc.fascinated.bat.autorole;
package cc.fascinated.bat.features.autorole;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
@ -19,7 +19,7 @@ import java.util.List;
* @author Fascinated (fascinated7)
*/
@Component
@Log4j2(topic = "AutoRole Listener")
@Log4j2
public class AutoRoleListener implements EventListener {
private final FeatureService featureService;

View File

@ -1,21 +1,19 @@
package cc.fascinated.bat.autorole.command;
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.RoleUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
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;
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;
@ -24,14 +22,14 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:add.sub")
@CommandInfo(name = "add", description = "Adds a role to the auto roles list")
public class AddSubCommand extends BatCommand {
public class AddSubCommand extends BatSubCommand {
@Autowired
public AddSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to add", true));
super.addOption(OptionType.ROLE, "role", "The role to add", true);
}
@Override
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 +42,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

View File

@ -0,0 +1,22 @@
package cc.fascinated.bat.features.autorole.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.context.ApplicationContext;
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)
public class AutoRoleCommand extends BatCommand {
public AutoRoleCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(ListSubCommand.class));
super.addSubCommand(context.getBean(AddSubCommand.class));
super.addSubCommand(context.getBean(RemoveSubCommand.class));
super.addSubCommand(context.getBean(ClearSubCommand.class));
}
}

View File

@ -1,14 +1,13 @@
package cc.fascinated.bat.autorole.command;
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
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,9 +17,9 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:clear.sub")
@CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatCommand {
public class ClearSubCommand extends BatSubCommand {
@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();

View File

@ -1,15 +1,14 @@
package cc.fascinated.bat.autorole.command;
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
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;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:list.sub")
@CommandInfo(name = "list", description = "Lists all auto roles")
public class ListSubCommand extends BatCommand {
public class ListSubCommand extends BatSubCommand {
@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()

View File

@ -1,20 +1,18 @@
package cc.fascinated.bat.autorole.command;
package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
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;
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;
@ -23,17 +21,22 @@ import org.springframework.stereotype.Component;
*/
@Component("autoroles:remove.sub")
@CommandInfo(name = "remove", description = "Removes a role from the auto roles list")
public class RemoveSubCommand extends BatCommand {
public class RemoveSubCommand extends BatSubCommand {
@Autowired
public RemoveSubCommand() {
super.addOptions(new OptionData(OptionType.ROLE, "role", "The role to remove", true));
super.addOption(OptionType.ROLE, "role", "The role to remove", true);
}
@Override
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())) {

View File

@ -1,7 +1,7 @@
package cc.fascinated.bat.autorole.profile;
package cc.fascinated.bat.features.autorole.profile;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter;

View File

@ -0,0 +1,45 @@
package cc.fascinated.bat.features.base;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.base.commands.botadmin.premium.PremiumAdminCommand;
import cc.fascinated.bat.features.base.commands.fun.EightBallCommand;
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;
import cc.fascinated.bat.features.base.commands.general.banner.BannerCommand;
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.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 BaseFeature extends Feature {
@Autowired
public BaseFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Base", false, Category.GENERAL);
super.registerCommand(commandService, context.getBean(PremiumCommand.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));
super.registerCommand(commandService, context.getBean(PingCommand.class));
super.registerCommand(commandService, context.getBean(InviteCommand.class));
super.registerCommand(commandService, context.getBean(HelpCommand.class));
super.registerCommand(commandService, context.getBean(BotStatsCommand.class));
super.registerCommand(commandService, context.getBean(BannerCommand.class));
super.registerCommand(commandService, context.getBean(AvatarCommand.class));
super.registerCommand(commandService, context.getBean(ImageCommand.class));
super.registerCommand(commandService, context.getBean(FeatureCommand.class));
super.registerCommand(commandService, context.getBean(EightBallCommand.class));
}
}

View File

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

View File

@ -1,19 +1,17 @@
package cc.fascinated.bat.base.commands.botadmin.premium;
package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
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;
@ -21,18 +19,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 BatSubCommand {
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));
super.addOption(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();

View File

@ -1,19 +1,17 @@
package cc.fascinated.bat.base.commands.botadmin.premium;
package cc.fascinated.bat.features.base.commands.botadmin.premium;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
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;
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;
@ -21,21 +19,19 @@ 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 BatSubCommand {
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),
new OptionData(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true)
);
super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true);
super.addOption(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", 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();

View File

@ -1,31 +1,23 @@
package cc.fascinated.bat.base.commands.fun;
package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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 = "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")
public class EightBallCommand extends BatCommand {
private final String[] responses = new String[]{
"It is certain",
@ -51,22 +43,20 @@ public class EightBallCommand extends BatCommand {
};
public EightBallCommand() {
super.addOptions(new OptionData(OptionType.STRING, "question", "The question you want to ask the 8ball", true));
super.addOption(OptionType.STRING, "question", "The question you want to ask the 8ball", true);
}
@Override
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()
.setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response))
);
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You asked: `%s`\n\n:8ball: The magic 8ball says: `%s`".formatted(question, response))
.build())
.queue();
}
}

View File

@ -1,15 +1,14 @@
package cc.fascinated.bat.base.commands.fun.image;
package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.thecatapi.CatImageToken;
import cc.fascinated.bat.model.BatGuild;
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;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "cat", description = "Get a random cat image")
public class CatSubCommand extends BatCommand {
public class CatSubCommand extends BatSubCommand {
@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();

View File

@ -1,15 +1,14 @@
package cc.fascinated.bat.base.commands.fun.image;
package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.dogceo.RandomImage;
import cc.fascinated.bat.model.BatGuild;
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;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "dog", description = "Get a random dog image")
public class DogSubCommand extends BatCommand {
public class DogSubCommand extends BatSubCommand {
@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();

View File

@ -1,15 +1,14 @@
package cc.fascinated.bat.base.commands.fun.image;
package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.randomd.RandomDuck;
import cc.fascinated.bat.model.BatGuild;
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;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "duck", description = "Get a random duck image")
public class DuckSubCommand extends BatCommand {
public class DuckSubCommand extends BatSubCommand {
@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();

View File

@ -1,15 +1,14 @@
package cc.fascinated.bat.base.commands.fun.image;
package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.common.model.token.randomfox.RandomFoxToken;
import cc.fascinated.bat.model.BatGuild;
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;
@ -19,9 +18,9 @@ import org.springframework.stereotype.Component;
*/
@Component
@CommandInfo(name = "fox", description = "Get a random fox image")
public class FoxSubCommand extends BatCommand {
public class FoxSubCommand extends BatSubCommand {
@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();

View File

@ -0,0 +1,24 @@
package cc.fascinated.bat.features.base.commands.fun.image;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "image", description = "View a random image", guildOnly = false, category = Category.FUN)
public class ImageCommand extends BatCommand {
@Autowired
public ImageCommand(@NonNull ApplicationContext context) {
super.addSubCommand(context.getBean(CatSubCommand.class));
super.addSubCommand(context.getBean(DogSubCommand.class));
super.addSubCommand(context.getBean(FoxSubCommand.class));
super.addSubCommand(context.getBean(DuckSubCommand.class));
}
}

View File

@ -1,18 +1,19 @@
package cc.fascinated.bat.base.commands.general;
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.*;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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.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;
import cc.fascinated.bat.service.GuildService;
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)
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()

View File

@ -0,0 +1,159 @@
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
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.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.commands.build.SubcommandData;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.interactions.components.selections.SelectOption;
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(name = "help", description = "View the bots command categories.", guildOnly = false)
public class HelpCommand extends BatCommand implements EventListener {
private final CommandService commandService;
@Autowired
public HelpCommand(@NonNull CommandService commandService) {
this.commandService = commandService;
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
event.replyEmbeds(createHomeEmbed()).addComponents(createHomeActions()).queue();
}
@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;
}
Category category = Category.getByName(item);
if (category == null) {
event.reply("Invalid category selected.").queue();
return;
}
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();
}
/**
* Creates the home embed for the help command
*
* @return The home embed
*/
private MessageEmbed createHomeEmbed() {
StringBuilder categories = new StringBuilder();
for (Category category : Category.getCategories()) {
long commandCount = commandService.getCommandsByCategory(category, true).size();
categories.append("➜ %s - **%s Command%s**\n".formatted(
category.getName(),
commandCount,
commandCount == 1 ? "" : "s"
));
}
return EmbedUtils.genericEmbed()
.setDescription("""
**Welcome to the Bat Help Menu!**
%s
*View our [TOS](%s) and [Privacy Policy](%s) for more information.*
""".formatted(
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")));
options.addAll(Category.getCategories().stream().map(category ->
SelectOption.of(category.getName(), category.getName()).withEmoji(category.getEmoji()))
.toList());
return new LayoutComponent[]{
ActionRow.of(
Button.of(ButtonStyle.LINK, Consts.INVITE_URL, "Invite"),
Button.of(ButtonStyle.LINK, Consts.SUPPORT_INVITE_URL, "Support")
),
ActionRow.of(
StringSelectMenu.create("help-menu")
.addOptions(options)
.build()
)
};
}
}

View File

@ -1,15 +1,13 @@
package cc.fascinated.bat.base.commands.general;
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.Consts;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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())

View File

@ -1,14 +1,12 @@
package cc.fascinated.bat.base.commands.general;
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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 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)
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(

View File

@ -1,15 +1,13 @@
package cc.fascinated.bat.base.commands.general;
package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
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.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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);

View File

@ -1,8 +1,7 @@
package cc.fascinated.bat.base.commands.general.avatar;
package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
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;
@ -12,13 +11,11 @@ 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) {
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
super.addSubCommand(context.getBean(GuildSubCommand.class));
super.addSubCommand(context.getBean(UserSubCommand.class));
}
}

View File

@ -1,14 +1,12 @@
package cc.fascinated.bat.base.commands.general.avatar;
package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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)
public class GuildSubCommand extends BatCommand {
@CommandInfo(name = "guild", description = "View the avatar of the guild")
public class GuildSubCommand extends BatSubCommand {
@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()))

View File

@ -1,38 +1,41 @@
package cc.fascinated.bat.base.commands.general.avatar;
package cc.fascinated.bat.features.base.commands.general.avatar;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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 Nick (okNick)
*/
@Component("avatar:user.sub")
@CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false, category = Category.GENERAL)
public class UserSubCommand extends BatCommand {
@CommandInfo(name = "user", description = "View the avatar of a user", guildOnly = false)
public class UserSubCommand extends BatSubCommand {
public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the avatar of", true));
super.addOption(OptionType.USER, "user", "The user to view the avatar of", true);
}
@Override
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())

View File

@ -1,8 +1,7 @@
package cc.fascinated.bat.base.commands.general.banner;
package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
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;
@ -12,13 +11,11 @@ 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) {
super.addSubCommands(
context.getBean(GuildSubCommand.class),
context.getBean(UserSubCommand.class)
);
super.addSubCommand(context.getBean(GuildSubCommand.class));
super.addSubCommand(context.getBean(UserSubCommand.class));
}
}

View File

@ -1,14 +1,12 @@
package cc.fascinated.bat.base.commands.general.banner;
package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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)
public class GuildSubCommand extends BatCommand {
@CommandInfo(name = "guild", description = "View the banner of the guild")
public class GuildSubCommand extends BatSubCommand {
@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()

View File

@ -1,20 +1,17 @@
package cc.fascinated.bat.base.commands.general.banner;
package cc.fascinated.bat.features.base.commands.general.banner;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
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 net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component;
@ -22,16 +19,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)
public class UserSubCommand extends BatCommand {
@CommandInfo(name = "user", description = "View the banner of a user", guildOnly = false)
public class UserSubCommand extends BatSubCommand {
public UserSubCommand() {
super.addOptions(new OptionData(OptionType.USER, "user", "The user to view the banner of", true));
super.addOption(OptionType.USER, "user", "The user to view the banner of", true);
}
@Override
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) {

View File

@ -0,0 +1,68 @@
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;
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.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)
*/
@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, @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 {
totalUsers++;
}
totalMembers++;
}
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription("""
**Member Count**
Total Members: `%s`
Total Users: `%s`
Total Bots: `%s`
\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),
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();
}
}

View File

@ -1,16 +1,15 @@
package cc.fascinated.bat.base.commands.server;
package cc.fascinated.bat.features.base.commands.server;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.model.BatGuild;
import cc.fascinated.bat.common.model.BatUser;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
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()) {

View File

@ -1,8 +1,8 @@
package cc.fascinated.bat.base.commands.server.channel;
package cc.fascinated.bat.features.base.commands.server.channel;
import cc.fascinated.bat.common.command.BatCommand;
import cc.fascinated.bat.common.command.Category;
import cc.fascinated.bat.common.command.CommandInfo;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -16,10 +16,8 @@ import org.springframework.stereotype.Component;
public class ChannelCommand extends BatCommand {
@Autowired
public ChannelCommand(@NonNull ApplicationContext context) {
super.addSubCommands(
context.getBean(ViewTopicSubCommand.class),
context.getBean(SetTopicSubCommand.class),
context.getBean(RemoveTopicSubCommand.class)
);
super.addSubCommand(context.getBean(ViewTopicSubCommand.class));
super.addSubCommand(context.getBean(SetTopicSubCommand.class));
super.addSubCommand(context.getBean(RemoveTopicSubCommand.class));
}
}

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