finish moderation
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 2m2s

This commit is contained in:
Lee 2024-07-09 19:46:48 +01:00
parent bc57834366
commit a3f4e2b918
50 changed files with 1312 additions and 137 deletions

@ -191,5 +191,10 @@
<artifactId>mcutils-java-library</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.github.ygimenez</groupId>
<artifactId>Pagination-Utils</artifactId>
<version>4.0.6</version>
</dependency>
</dependencies>
</project>

@ -3,18 +3,17 @@ package cc.fascinated.bat.command;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.*;
import net.dv8tion.jda.api.entities.Member;
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 org.codehaus.plexus.util.cli.Arg;
import java.util.Collections;
import java.util.EnumSet;
@ -143,4 +142,56 @@ public abstract class BatCommand {
}
return null;
}
/**
* Get an argument from the command.
*
* @param argumentIndex the index of the argument in the command
* @param option the option to get from the slash command
* @param arguments the arguments of the invoked command
* @param event the event that invoked the command
* @return the argument
*/
public Argument getArgument(int argumentIndex, String option, String[] arguments, SlashCommandInteraction event) {
return new Argument(argumentIndex, option, arguments, event);
}
@AllArgsConstructor
public static class Argument {
/**
* The index of the argument in the command.
*/
private final int argumentIndex;
/**
* The option to get from the slash command.
*/
private final String option;
/**
* The arguments of the invoked command.
*/
private final String[] arguments;
/**
* 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) {
OptionMapping option = event.getOption(this.option);
if (option == null) {
return null;
}
return option.getAsString();
}
return arguments[argumentIndex];
}
}
}

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

@ -26,6 +26,17 @@ public class RoleUtils {
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
*

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.autorole.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild;
@ -90,7 +90,7 @@ public class SyncSubCommand extends BatCommand {
// We're finished giving all the roles
if (finished == members.size()) {
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(
DescriptionBuilder description = new DescriptionBuilder(
"Successfully gave auto roles to `%s` member%s".formatted(
members.size(),
members.size() == 1 ? "" : "s"

@ -9,7 +9,6 @@ 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.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;
@ -50,9 +49,12 @@ public class EightBallCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping questionOption = event.getOption("question");
assert questionOption != null;
String question = questionOption.getAsString();
String question = super.getArgument(0, "question", null, event).getAsString();
if (question == null) {
// todo: reply
return;
}
String response = responses[(int) (Math.random() * responses.length)];
event.replyEmbeds(EmbedUtils.successEmbed()

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.base.commands.fun;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.MathUtils;
import cc.fascinated.bat.model.BatGuild;
@ -41,7 +41,7 @@ public class PPSizeCommand extends BatCommand {
int size = (int) MathUtils.random(1, 12);
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("PP Size")
.setDescription(new DescriptionBuilder("PP Size")
.appendLine("""
The size of %s's pp is %s inches
**8%sD**

@ -43,7 +43,7 @@ public class BotStatsCommand extends BatCommand {
long memoryUsed = (runtime.totalMemory() - runtime.freeMemory());
event.replyEmbeds(EmbedUtils.genericEmbed().setDescription(
new EmbedDescriptionBuilder("Bat Statistics")
new DescriptionBuilder("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)

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.base.commands.general;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
@ -26,7 +26,7 @@ public class VoteCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Vote Links");
DescriptionBuilder description = new DescriptionBuilder("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);

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.base.commands.utility;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
import cc.fascinated.bat.model.BatGuild;
@ -43,7 +43,7 @@ public class PastebinCommand extends BatCommand {
// Upload the text to pastebin
String url = PasteUtils.uploadPaste(text).getUrl();
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("The text has been uploaded to Paste!")
.setDescription(new DescriptionBuilder("The text has been uploaded to Paste!")
.appendLine("URL: %s".formatted(url), true)
.build())
.build())

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.base.commands.utility.lookup;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.LongUtils;
import cc.fascinated.bat.model.BatGuild;
@ -61,7 +61,7 @@ public class UserSubCommand extends BatCommand {
}
String name = target.getGlobalName() == null ? target.getName() : target.getGlobalName().replaceAll("`", "");
target.retrieveProfile().queue(profile -> event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("User Lookup")
.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)

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.leveling.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.features.leveling.LevelingFeature;
@ -47,7 +47,7 @@ public class CurrentSubCommand extends BatCommand {
LevelingProfile profile = guild.getLevelingProfile();
UserLevel level = profile.getUserLevel(target.getId());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Current Level");
DescriptionBuilder description = new DescriptionBuilder("Current Level");
description.appendLine("User: %s".formatted(target.getDiscordUser().getAsMention()), true);
description.appendLine("Level: `%s`".formatted(level.getLevel()), true);
description.appendLine("XP: `%s`/`%s`".formatted(

@ -14,7 +14,8 @@ public enum LogCategory {
MESSAGE(Emoji.fromUnicode("📩")),
MEMBER(Emoji.fromUnicode("👤")),
CHANNEL(Emoji.fromUnicode("📺")),
GUILD(Emoji.fromUnicode("🏰"));
GUILD(Emoji.fromUnicode("🏰")),
MODERATION(Emoji.fromUnicode("🛡️"));
/**
* The emoji of the log category

@ -18,9 +18,12 @@ import org.springframework.stereotype.Component;
*/
@Component
public class LogFeature extends Feature {
public static LogFeature INSTANCE;
@Autowired
public LogFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
super("Logging", FeatureProfile.FeatureState.DISABLED, true);
INSTANCE = this;
super.registerCommand(commandService, context.getBean(LogsCommand.class));
}

@ -60,7 +60,14 @@ public enum LogType {
STICKER_REMOVE(LogCategory.GUILD),
STICKER_NAME_UPDATED(LogCategory.GUILD),
BOOST_TIER_UPDATED(LogCategory.GUILD),
GUILD_CONFIGURATION(LogCategory.GUILD);
GUILD_CONFIGURATION(LogCategory.GUILD),
/**
* Moderation Events
*/
PUNISHMENT_ISSUED(LogCategory.MODERATION),
PUNISHMENT_REMOVED_OR_EXPIRED(LogCategory.MODERATION),
PURGE(LogCategory.MODERATION);
/**
* The category of the log type

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.logging.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.logging.LogCategory;
@ -78,7 +78,7 @@ public class ListSubCommand extends BatCommand implements EventListener {
}
List<LogType> logEvents = LogType.getLogTypesByCategory(category.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(null);
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("Log channels for the `%s` category".formatted(category.getName()), false);
description.emptyLine();
for (LogType logType : logEvents) {
@ -97,7 +97,7 @@ public class ListSubCommand extends BatCommand implements EventListener {
* @return the help message
*/
public MessageEmbed getHelpMessage() {
return EmbedUtils.genericEmbed().setDescription(new EmbedDescriptionBuilder(null).appendLine(
return EmbedUtils.genericEmbed().setDescription(new DescriptionBuilder(null).appendLine(
"""
Set the log channel for:
- A specific event, use `/logs set <event> <channel>`

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.logging.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.logging.LogCategory;
import cc.fascinated.bat.features.logging.LogProfile;
@ -44,7 +44,7 @@ public class RemoveSubCommand extends BatCommand {
for (LogType logType : LogType.values()) {
profile.removeLogChannel(logType);
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(null);
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("%s Successfully removed the log channel from `%s` log events".formatted(
Emojis.CHECK_MARK_EMOJI,
LogType.values().length
@ -62,7 +62,7 @@ public class RemoveSubCommand extends BatCommand {
for (LogType logType : category) {
profile.removeLogChannel(logType);
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(null);
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("%s Successfully removed the log channel from `%s` `%s` log events".formatted(
Emojis.CHECK_MARK_EMOJI,
category.size(),
@ -85,7 +85,7 @@ public class RemoveSubCommand extends BatCommand {
return;
}
profile.removeLogChannel(logType);
event.replyEmbeds(EmbedUtils.successEmbed().setDescription(new EmbedDescriptionBuilder(null)
event.replyEmbeds(EmbedUtils.successEmbed().setDescription(new DescriptionBuilder(null)
.appendLine("%s Successfully removed the log channel from `%s`".formatted(
Emojis.CHECK_MARK_EMOJI,
logType.getName()

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.logging.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.logging.LogCategory;
import cc.fascinated.bat.features.logging.LogProfile;
@ -51,7 +51,7 @@ public class SetSubCommand extends BatCommand {
for (LogType logType : LogType.values()) {
profile.setLogChannel(logType, targetChannel);
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(null);
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("%s Successfully set the log channel for `%s` log events to %s".formatted(
Emojis.CHECK_MARK_EMOJI,
LogType.values().length,
@ -70,7 +70,7 @@ public class SetSubCommand extends BatCommand {
for (LogType logType : category) {
profile.setLogChannel(logType, targetChannel);
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder(null);
DescriptionBuilder description = new DescriptionBuilder(null);
description.appendLine("%s Successfully set the log channel for `%s` `%s` log events to %s".formatted(
Emojis.CHECK_MARK_EMOJI,
category.size(),
@ -94,7 +94,7 @@ public class SetSubCommand extends BatCommand {
return;
}
profile.setLogChannel(logType, targetChannel);
event.replyEmbeds(EmbedUtils.successEmbed().setDescription(new EmbedDescriptionBuilder(null)
event.replyEmbeds(EmbedUtils.successEmbed().setDescription(new DescriptionBuilder(null)
.appendLine("%s Successfully set the log channel for `%s` to %s".formatted(
Emojis.CHECK_MARK_EMOJI,
logType.getName(),

@ -84,7 +84,7 @@ public class ChannelListener implements EventListener {
String type = formatChannelType(channel);
log.info("{} \"{}\" was created in guild \"{}\"", type, event.getChannel().getName(), guild.getName());
logFeature.sendLog(guild, LogType.CHANNEL_CREATE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("%s Created".formatted(type))
.setDescription(new DescriptionBuilder("%s Created".formatted(type))
.appendLine("%s: %s".formatted(type, event.getChannel().getAsMention()), true)
.appendLine("Name: `%s`".formatted(event.getChannel().getName()), true)
.build())
@ -96,7 +96,7 @@ public class ChannelListener implements EventListener {
ChannelUnion channel = event.getChannel();
String type = formatChannelType(channel);
log.info("{} \"{}\" was deleted in guild \"{}\"", type, event.getChannel().getName(), guild.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("%s Deleted".formatted(type))
DescriptionBuilder description = new DescriptionBuilder("%s Deleted".formatted(type))
.appendLine("Name: `#%s`".formatted(channel.getName()), true);
if (channel instanceof TextChannel) {
TextChannel textChannel = channel.asTextChannel();
@ -125,7 +125,7 @@ public class ChannelListener implements EventListener {
// User switched channels
if (oldChannel != null && !oldChannel.equals(voiceChannel)) {
log.info("User \"{}\" switched from voice channel \"{}\" to \"{}\" in guild \"{}\"", user.getName(), oldChannel.getName(), voiceChannel.getName(), guild.getName());
String switchDescription = new EmbedDescriptionBuilder("Member Switched Voice Channel")
String switchDescription = new DescriptionBuilder("Member Switched Voice Channel")
.appendLine("User: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Channel: %s -> %s".formatted(oldChannel.getAsMention(), voiceChannel.getAsMention()), true)
.build();
@ -134,7 +134,7 @@ public class ChannelListener implements EventListener {
}
log.info("User \"{}\" {} voice channel \"{}\" in guild \"{}\"", user.getName(), joined ? "joined" : "left", voiceChannel.getName(), guild.getName());
String description = new EmbedDescriptionBuilder("Member %s Voice Channel".formatted(joined ? "Joined" : "Left"))
String description = new DescriptionBuilder("Member %s Voice Channel".formatted(joined ? "Joined" : "Left"))
.appendLine("User: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Channel: %s".formatted(voiceChannel.getAsMention()), true)
.build();
@ -161,7 +161,7 @@ public class ChannelListener implements EventListener {
String target = event.getPermissionOverride().isRoleOverride() ? event.getPermissionOverride().getRole().getAsMention()
: event.getPermissionOverride().getMember().getAsMention();
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Channel permissions updated")
DescriptionBuilder description = new DescriptionBuilder("Channel permissions updated")
.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true)
.appendLine("%s: %s".formatted(
event.getPermissionOverride().isRoleOverride() ? "Role" : "Member",
@ -189,7 +189,7 @@ public class ChannelListener implements EventListener {
String type = formatChannelType(channel);
log.info("{} \"{}\" topic was updated to {} in guild \"{}\"", type, newValue, oldValue, guild.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("%s Topic Updated".formatted(type));
DescriptionBuilder description = new DescriptionBuilder("%s Topic Updated".formatted(type));
description.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true);
description.appendLine("Topic: `%s` -> `%s`".formatted(
oldValue == null ? "No Topic" : oldValue,
@ -208,7 +208,7 @@ public class ChannelListener implements EventListener {
VoiceChannel channel = event.getChannel().asVoiceChannel();
log.info("Voice channel \"{}\" bitrate was updated to {} in guild \"{}\"", channel.getName(), event.getNewValue(), guild.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Voice Channel Updated")
DescriptionBuilder description = new DescriptionBuilder("Voice Channel Updated")
.appendLine("Channel: %s".formatted(channel.getAsMention()), true)
.appendLine("Bitrate: `%s` -> `%s`".formatted(NumberFormatter.format(event.getOldValue()),
NumberFormatter.format(event.getNewValue())), true);
@ -225,7 +225,7 @@ public class ChannelListener implements EventListener {
String type = formatChannelType(channel);
log.info("{} \"{}\" name was updated to {} in guild \"{}\"", type, newValue, oldValue, guild.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("%s Name Updated".formatted(type));
DescriptionBuilder description = new DescriptionBuilder("%s Name Updated".formatted(type));
if (channel.getType() == ChannelType.TEXT || channel.getType() == ChannelType.VOICE) {
description.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true);
}
@ -239,7 +239,7 @@ public class ChannelListener implements EventListener {
@Override
public void onChannelUpdateNSFW(@NonNull BatGuild guild, @NonNull ChannelUpdateNSFWEvent event) {
logFeature.sendLog(guild, LogType.CHANNEL_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Channel NSFW Updated")
.setDescription(new DescriptionBuilder("Channel NSFW Updated")
.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true)
.appendLine("NSFW: `%s` -> `%s`".formatted(
Boolean.TRUE.equals(event.getOldValue()) ? "Yes" : "No",
@ -257,7 +257,7 @@ public class ChannelListener implements EventListener {
VoiceChannel channel = event.getChannel().asVoiceChannel();
log.info("Voice channel \"{}\" user limit was updated to {} in guild \"{}\"", channel.getName(), event.getNewValue(), guild.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Voice Channel User Limit Updated")
DescriptionBuilder description = new DescriptionBuilder("Voice Channel User Limit Updated")
.appendLine("Channel: %s".formatted(channel.getAsMention()), true)
.appendLine("User Limit: `%s` -> `%s`".formatted(
event.getOldValue() == 0 ? "Unlimited" : NumberFormatter.format(event.getOldValue()),
@ -277,7 +277,7 @@ public class ChannelListener implements EventListener {
String newRegion = event.getNewValue() == Region.UNKNOWN ? "Automatic" : event.getNewValue().getName();
log.info("Channel \"{}\" region was updated to \"{}\" in guild \"{}\"", event.getChannel().getName(), newRegion, guild.getName());
logFeature.sendLog(guild, LogType.CHANNEL_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Channel Region Updated")
.setDescription(new DescriptionBuilder("Channel Region Updated")
.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true)
.appendLine("Region: `%s` -> `%s`".formatted(
event.getOldValue() == Region.UNKNOWN ? "Automatic" : event.getOldValue().getName(),
@ -297,7 +297,7 @@ public class ChannelListener implements EventListener {
log.info("Text channel \"{}\" slowmode was updated to {} in guild \"{}\"", event.getChannel().getName(), newSlowmode, guild.getName());
logFeature.sendLog(guild, LogType.CHANNEL_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Channel Slowmode Updated")
.setDescription(new DescriptionBuilder("Channel Slowmode Updated")
.appendLine("Channel: %s".formatted(event.getChannel().getAsMention()), true)
.appendLine("Slowmode: `%s` -> `%s`".formatted(oldSlowmode, newSlowmode), true)
.build())
@ -308,7 +308,7 @@ public class ChannelListener implements EventListener {
public void onChannelUpdateArchived(@NonNull BatGuild guild, @NonNull Channel channel, boolean isArchived, @NonNull ChannelUpdateArchivedEvent event) {
log.info("Thread \"{}\" was {} in guild \"{}\"", channel.getName(), isArchived ? "archived" : "unarchived", guild.getName());
logFeature.sendLog(guild, LogType.THREAD_ARCHIVE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Thread %s".formatted(isArchived ? "Archived" : "Unarchived"))
.setDescription(new DescriptionBuilder("Thread %s".formatted(isArchived ? "Archived" : "Unarchived"))
.appendLine("Channel: %s".formatted(channel.getAsMention()), true)
.build())
.build());

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.logging.LogFeature;
@ -32,7 +32,7 @@ public class EmojiListener implements EventListener {
public void onEmojiAdd(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull EmojiAddedEvent event) {
log.info("Emoji \"{}\" was added in guild \"{}\"", emoji.getName(), guild.getName());
logFeature.sendLog(guild, LogType.EMOJI_ADD, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Emoji Added")
.setDescription(new DescriptionBuilder("Emoji Added")
.appendLine("Emoji: %s".formatted(emoji.getFormatted()), true)
.appendLine("Name: `%s`".formatted(emoji.getName()), true)
.build())
@ -43,7 +43,7 @@ public class EmojiListener implements EventListener {
public void onEmojiRemove(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull EmojiRemovedEvent event) {
log.info("Emoji \"{}\" was removed in guild \"{}\"", emoji.getName(), guild.getName());
logFeature.sendLog(guild, LogType.EMOJI_REMOVE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Emoji Removed")
.setDescription(new DescriptionBuilder("Emoji Removed")
.appendLine("Emoji: %s".formatted(emoji.getFormatted()), true)
.appendLine("Name: `%s`".formatted(emoji.getName()), true)
.build())
@ -54,7 +54,7 @@ public class EmojiListener implements EventListener {
public void onEmojiRename(@NonNull BatGuild guild, @NonNull Emoji emoji, @NonNull String oldName, @NonNull String newName, @NonNull EmojiUpdateNameEvent event) {
log.info("Emoji \"{}\" was renamed to \"{}\" in guild \"{}\"", oldName, newName, guild.getName());
logFeature.sendLog(guild, LogType.EMOJI_NAME_UPDATED, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Emoji Renamed")
.setDescription(new DescriptionBuilder("Emoji Renamed")
.appendLine("Emoji: %s".formatted(emoji.getFormatted()), true)
.appendLine("Name: `%s` -> `%s`".formatted(oldName, newName), true)
.build())

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.EnumUtils;
import cc.fascinated.bat.common.TimeUtils;
@ -41,7 +41,7 @@ public class GuildListener implements EventListener {
log.info("Invite created in guild \"{}\" with code \"{}\"", guild.getGuild().getName(), invite.getCode());
invite.expand().queue(expandedInvite -> {
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Invite Created");
DescriptionBuilder description = new DescriptionBuilder("Invite Created");
description.appendLine("Invite: %s".formatted(expandedInvite.getUrl()), true);
if (expandedInvite.getChannel() != null) {
description.appendLine("Channel: %s".formatted(
@ -60,7 +60,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateName(@NonNull BatGuild guild, String oldName, String newName, @NonNull GuildUpdateNameEvent event) {
log.info("Guild name was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldName, newName);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Name Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Name Updated")
.appendLine("Name: `%s` -> `%s`".formatted(oldName, newName), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -69,7 +69,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateAfkChannel(@NonNull BatGuild guild, VoiceChannel oldChannel, VoiceChannel newChannel, @NonNull GuildUpdateAfkChannelEvent event) {
log.info("Guild AFK channel was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldChannel == null ? "None" : oldChannel.getName(), newChannel == null ? "None" : newChannel.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild AFK Channel Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild AFK Channel Updated")
.appendLine("Channel: `%s` -> `%s`".formatted(
oldChannel == null ? "None" : oldChannel.getName(),
newChannel == null ? "None" : newChannel.getName()
@ -82,7 +82,7 @@ public class GuildListener implements EventListener {
log.info("Guild AFK timeout was updated in guild \"{}\" from \"{}\" to \"{}\"",
guild.getGuild().getName(), oldTimeout.getSeconds(), newTimeout.getSeconds());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild AFK Timeout Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild AFK Timeout Updated")
.appendLine("Timeout: `%s` -> `%s`".formatted(
TimeUtils.format(oldTimeout.getSeconds() * 1000L),
TimeUtils.format(newTimeout.getSeconds() * 1000L)
@ -94,7 +94,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateBanner(@NonNull BatGuild guild, String oldBannerUrl, String newBannerUrl, @NonNull GuildUpdateBannerEvent event) {
log.info("Guild banner was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldBannerUrl, newBannerUrl);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Banner Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Banner Updated")
.appendLine("Banner: [click here](%s)".formatted(newBannerUrl), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -103,7 +103,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateDescription(@NonNull BatGuild guild, String oldDescription, String newDescription, @NonNull GuildUpdateDescriptionEvent event) {
log.info("Guild description was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldDescription, newDescription);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Description Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Description Updated")
.appendLine("Description: `%s` -> `%s`".formatted(oldDescription, newDescription), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -112,7 +112,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateIcon(@NonNull BatGuild guild, String oldIconUrl, String newIconUrl, @NonNull GuildUpdateIconEvent event) {
log.info("Guild icon was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldIconUrl, newIconUrl);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Icon Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Icon Updated")
.appendLine("Icon: [click here](%s)".formatted(newIconUrl), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -121,7 +121,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateLocale(@NonNull BatGuild guild, DiscordLocale oldLocale, DiscordLocale newLocale, @NonNull GuildUpdateLocaleEvent event) {
log.info("Guild primary language was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLocale, newLocale);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Primary Language Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Primary Language Updated")
.appendLine("Language: `%s` -> `%s`".formatted(
oldLocale.getLanguageName(),
newLocale.getLanguageName()
@ -133,7 +133,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateCommunityUpdatesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateCommunityUpdatesChannelEvent event) {
log.info("Guild community updates channel was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldChannel == null ? "None" : oldChannel.getName(), newChannel == null ? "None" : newChannel.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Community Updates Channel Updated")
DescriptionBuilder description = new DescriptionBuilder("Community Updates Channel Updated")
.appendLine("Channel: `%s` -> `%s`".formatted(
oldChannel == null ? "None" : oldChannel.getName(),
newChannel == null ? "None" : newChannel.getName()
@ -146,7 +146,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateBoostTier(@NonNull BatGuild guild, Guild.BoostTier oldTier, Guild.BoostTier newTier, @NonNull GuildUpdateBoostTierEvent event) {
log.info("Guild boost tier was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldTier.getKey(), newTier.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Boost Tier Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Boost Tier Updated")
.appendLine("Tier: `%s` -> `%s`".formatted(oldTier.getKey(), newTier.getKey()), true);
logFeature.sendLog(guild, LogType.BOOST_TIER_UPDATED, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -155,7 +155,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateMaxMembers(@NonNull BatGuild guild, int oldCount, int newCount, @NonNull GuildUpdateMaxMembersEvent event) {
log.info("Guild max members was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldCount, newCount);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Max Members Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Max Members Updated")
.appendLine("Members: `%s` -> `%s`".formatted(oldCount, newCount), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -164,7 +164,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateExplicitContentLevel(@NonNull BatGuild guild, Guild.ExplicitContentLevel oldLevel, Guild.ExplicitContentLevel newLevel, @NonNull GuildUpdateExplicitContentLevelEvent event) {
log.info("Guild explicit content level was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLevel.getKey(), newLevel.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Explicit Content Level Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Explicit Content Level Updated")
.appendLine("Level: `%s` -> `%s`".formatted(
EnumUtils.getEnumName(oldLevel),
EnumUtils.getEnumName(newLevel)
@ -176,7 +176,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateMFALevel(@NonNull BatGuild guild, Guild.MFALevel oldLevel, Guild.MFALevel newLevel, @NonNull GuildUpdateMFALevelEvent event) {
log.info("Guild MFA level was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLevel.getKey(), newLevel.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild MFA Level Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild MFA Level Updated")
.appendLine("Level: `%s` -> `%s`".formatted(
EnumUtils.getEnumName(oldLevel),
EnumUtils.getEnumName(newLevel)
@ -188,7 +188,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateNotificationLevel(@NonNull BatGuild guild, Guild.NotificationLevel oldLevel, Guild.NotificationLevel newLevel, @NonNull GuildUpdateNotificationLevelEvent event) {
log.info("Guild notification level was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLevel.getKey(), newLevel.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Notification Level Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Notification Level Updated")
.appendLine("Level: `%s` -> `%s`".formatted(
EnumUtils.getEnumName(oldLevel),
EnumUtils.getEnumName(newLevel)
@ -200,7 +200,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateNSFWLevel(@NonNull BatGuild guild, Guild.NSFWLevel oldLevel, Guild.NSFWLevel newLevel, @NonNull GuildUpdateNSFWLevelEvent event) {
log.info("Guild NSFW level was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLevel.getKey(), newLevel.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild NSFW Level Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild NSFW Level Updated")
.appendLine("Level: `%s` -> `%s`".formatted(
EnumUtils.getEnumName(oldLevel),
EnumUtils.getEnumName(newLevel)
@ -212,7 +212,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateOwner(@NonNull BatGuild guild, @NonNull BatUser oldOwner, @NonNull BatUser newOwner, @NonNull GuildUpdateOwnerEvent event) {
log.info("Guild owner was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldOwner.getUser().getAsTag(), newOwner.getUser().getAsTag());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Owner Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Owner Updated")
.appendLine("Owner: %s -> %s".formatted(oldOwner.getUser().getAsMention(), newOwner.getUser().getAsMention()), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}
@ -221,7 +221,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateRulesChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateRulesChannelEvent event) {
log.info("Guild rules channel was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldChannel == null ? "None" : oldChannel.getName(), newChannel == null ? "None" : newChannel.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Rules Channel Updated")
DescriptionBuilder description = new DescriptionBuilder("Rules Channel Updated")
.appendLine("Channel: %s -> %s".formatted(
oldChannel == null ? "`None`" : oldChannel.getAsMention(),
newChannel == null ? "`None`" : newChannel.getAsMention()
@ -233,7 +233,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateSystemChannel(@NonNull BatGuild guild, TextChannel oldChannel, TextChannel newChannel, @NonNull GuildUpdateSystemChannelEvent event) {
log.info("Guild system channel was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldChannel == null ? "None" : oldChannel.getName(), newChannel == null ? "None" : newChannel.getName());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("System Channel Updated")
DescriptionBuilder description = new DescriptionBuilder("System Channel Updated")
.appendLine("Channel: %s -> %s".formatted(
oldChannel == null ? "`None`" : oldChannel.getAsMention(),
newChannel == null ? "`None`" : newChannel.getAsMention()
@ -245,7 +245,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateVanityCode(@NonNull BatGuild guild, String oldCode, String newCode, @NonNull GuildUpdateVanityCodeEvent event) {
log.info("Guild vanity code was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldCode, newCode);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Vanity Code Updated")
DescriptionBuilder description = new DescriptionBuilder("Vanity Code Updated")
.appendLine("Code: `%s` -> `%s`".formatted(
oldCode == null ? "None" : oldCode,
newCode == null ? "None" : newCode
@ -257,7 +257,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateVerificationLevel(@NonNull BatGuild guild, Guild.VerificationLevel oldLevel, Guild.VerificationLevel newLevel, @NonNull GuildUpdateVerificationLevelEvent event) {
log.info("Guild verification level was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldLevel.getKey(), newLevel.getKey());
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Verification Level Updated")
DescriptionBuilder description = new DescriptionBuilder("Verification Level Updated")
.appendLine("Level: `%s` -> `%s`".formatted(
EnumUtils.getEnumName(oldLevel),
EnumUtils.getEnumName(newLevel)
@ -269,7 +269,7 @@ public class GuildListener implements EventListener {
public void onGuildUpdateSplash(@NonNull BatGuild guild, String oldSplashUrl, String newSplashUrl, @NonNull GuildUpdateSplashEvent event) {
log.info("Guild splash was updated in guild \"{}\" from \"{}\" to \"{}\"", guild.getGuild().getName(), oldSplashUrl, newSplashUrl);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Guild Splash Updated")
DescriptionBuilder description = new DescriptionBuilder("Guild Splash Updated")
.appendLine("Splash: [click here](%s)".formatted(newSplashUrl), true);
logFeature.sendLog(guild, LogType.GUILD_CONFIGURATION, EmbedUtils.successEmbed().setDescription(description.build()).build());
}

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.logging.LogFeature;
@ -53,7 +53,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" joined the guild \"{}\"", user.getName(), guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_JOIN, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Joined")
.setDescription(new DescriptionBuilder("Member Joined")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Username: `%s`".formatted(user.getDiscordUser().getName()), true)
.appendLine("Joined Discord: <t:%s:R>".formatted(user.getDiscordUser().getTimeCreated().toEpochSecond()), true)
@ -68,7 +68,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" left the guild \"{}\"", user.getName(), guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_LEAVE, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Left")
.setDescription(new DescriptionBuilder("Member Left")
.appendLine("Member: <@%s>".formatted(user.getId()), true)
.appendLine("Username: `%s`".formatted(user.getName()), true)
.build())
@ -86,7 +86,7 @@ public class MemberListener implements EventListener {
user.getName(), oldNameFormatted, newNameFormatted, guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_NICKNAME_UPDATE, EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Nickname Updated")
.setDescription(new DescriptionBuilder("Member Nickname Updated")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Nickname: `%s` -> `%s`".formatted(
oldNameFormatted,
@ -111,7 +111,7 @@ public class MemberListener implements EventListener {
if (!guild.isMember(user.getDiscordUser())) continue; // User is not in the guild
logFeature.sendLog(batGuild, LogType.MEMBER_GLOBAL_NAME_UPDATE, EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Name Updated")
.setDescription(new DescriptionBuilder("Member Name Updated")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Name: `%s` -> `%s`".formatted(
oldNameFormatted,
@ -135,7 +135,7 @@ public class MemberListener implements EventListener {
if (!guild.isMember(user.getDiscordUser())) continue; // User is not in the guild
logFeature.sendLog(batGuild, LogType.MEMBER_USERNAME_UPDATE, EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Username Updated")
.setDescription(new DescriptionBuilder("Member Username Updated")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Username: `%s` -> `%s`".formatted(oldName, newName), true)
.build())
@ -157,7 +157,7 @@ public class MemberListener implements EventListener {
if (!guild.isMember(user.getDiscordUser())) continue; // User is not in the guild
logFeature.sendLog(batGuild, LogType.MEMBER_USERNAME_UPDATE, EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Avatar Updated")
.setDescription(new DescriptionBuilder("Member Avatar Updated")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Avatar: %s".formatted(newAvatarUrl == null ? "Removed" : "[click here](%s)".formatted(newAvatarUrl)), true)
.build())
@ -180,7 +180,7 @@ public class MemberListener implements EventListener {
String s = rolesAdded.size() > 1 ? "s" : "";
logFeature.sendLog(guild, LogType.MEMBER_ROLE_UPDATE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Role%s Added".formatted(s))
.setDescription(new DescriptionBuilder("Member Role%s Added".formatted(s))
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Role%s Added: %s".formatted(s, roles.substring(0, roles.length() - 2)), true)
.build())
@ -201,7 +201,7 @@ public class MemberListener implements EventListener {
String s = rolesAdded.size() > 1 ? "s" : "";
logFeature.sendLog(guild, LogType.MEMBER_ROLE_UPDATE, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Role%s Removed".formatted(s))
.setDescription(new DescriptionBuilder("Member Role%s Removed".formatted(s))
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Role%s Removed: %s".formatted(s, roles.substring(0, roles.length() - 2)), true)
.build())
@ -216,7 +216,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" was banned from the guild \"{}\"", user.getName(), guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_BAN, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Banned")
.setDescription(new DescriptionBuilder("Member Banned")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.build())
.build());
@ -230,7 +230,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" was unbanned from the guild \"{}\"", user.getName(), guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_UNBAN, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Unbanned")
.setDescription(new DescriptionBuilder("Member Unbanned")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.build())
.build());
@ -244,7 +244,7 @@ public class MemberListener implements EventListener {
long seconds = timeoutEnd.toInstant().getEpochSecond();
logFeature.sendLog(guild, LogType.MEMBER_TIMEOUT, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Timed Out")
.setDescription(new DescriptionBuilder("Member Timed Out")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Timeout End: <t:%s>".formatted(seconds), true)
.appendLine("Relative End: <t:%s:R>".formatted(seconds), true)
@ -261,7 +261,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" boosted the guild \"{}\" until \"{}\"", user.getName(), guild.getDiscordGuild().getName(), newBoostTime);
logFeature.sendLog(guild, LogType.MEMBER_BOOSTED, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Boosted")
.setDescription(new DescriptionBuilder("Member Boosted")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.build())
.build());
@ -279,7 +279,7 @@ public class MemberListener implements EventListener {
log.info("User \"{}\" boost expired in the guild \"{}\"", user.getName(), guild.getDiscordGuild().getName());
logFeature.sendLog(guild, LogType.MEMBER_BOOST_EXPIRE, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Member Boost Expired")
.setDescription(new DescriptionBuilder("Member Boost Expired")
.appendLine("Member: %s".formatted(user.getDiscordUser().getAsMention()), true)
.build())
.build());

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.logging.LogFeature;
@ -35,7 +35,7 @@ public class MessageListener implements EventListener {
log.info("User \"{}\" deleted a message in guild \"{}\"", user.getDiscordUser().getGlobalName(), guild.getName());
logFeature.sendLog(guild, LogType.MESSAGE_DELETE, EmbedUtils.errorEmbed()
.setDescription(new EmbedDescriptionBuilder("Message Deleted")
.setDescription(new DescriptionBuilder("Message Deleted")
.appendLine("Author: %s".formatted(message.getAuthor().getAsMention()), true)
.appendLine("Channel: %s".formatted(message.getChannel().getAsMention()), true)
.appendLine("Content: %s".formatted(logFeature.formatContent(message.getContent())), true)
@ -50,7 +50,7 @@ public class MessageListener implements EventListener {
log.info("User \"{}\" edited a message in guild \"{}\"", user.getDiscordUser().getGlobalName(), guild.getName());
logFeature.sendLog(guild, LogType.MESSAGE_EDIT, EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Message Edited")
.setDescription(new DescriptionBuilder("Message Edited")
.appendLine("Author: %s".formatted(newMessage.getAuthor().getAsMention()), true)
.appendLine("Channel: %s".formatted(newMessage.getChannel().getAsMention()), true)
.appendLine("Old Content: %s".formatted(logFeature.formatContent(oldMessage.getContent())), true)

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.RoleUtils;
import cc.fascinated.bat.event.EventListener;
@ -53,7 +53,7 @@ public class RoleListener implements EventListener {
}
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Role Permissions Updated")
DescriptionBuilder description = new DescriptionBuilder("Role Permissions Updated")
.appendLine("Role: %s".formatted(event.getRole().getAsMention()), true);
if (!allowedPermissions.isEmpty()) {
description.appendLine("Allowed Permissions: %s".formatted(allowedPermissions.substring(0, allowedPermissions.length() - 2)), true);
@ -72,7 +72,7 @@ public class RoleListener implements EventListener {
Role role = event.getRole();
log.info("Role \"{}\" was created in guild \"{}\"", role.getName(), guild.getName());
logFeature.sendLog(guild, LogType.ROLE_CREATE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Created")
.setDescription(new DescriptionBuilder("Role Created")
.appendLine("Role: %s".formatted(role.getAsMention()), true)
.appendLine("Color: %s".formatted(RoleUtils.getFormattedColor(role)), true)
.appendLine("Permissions: %s".formatted(RoleUtils.getFormattedPermissions(role)), true)
@ -85,7 +85,7 @@ public class RoleListener implements EventListener {
Role role = event.getRole();
log.info("Role \"{}\" was deleted in guild \"{}\"", role.getName(), guild.getName());
logFeature.sendLog(guild, LogType.ROLE_DELETE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Deleted")
.setDescription(new DescriptionBuilder("Role Deleted")
.appendLine("Role: `%s`".formatted(role.getName()), true)
.appendLine("Color: %s".formatted(RoleUtils.getFormattedColor(role)), true)
.appendLine("Permissions: %s".formatted(RoleUtils.getFormattedPermissions(role)), true)
@ -99,7 +99,7 @@ public class RoleListener implements EventListener {
String newName = event.getNewName();
log.info("Role \"{}\" was renamed to \"{}\" in guild \"{}\"", oldName, newName, guild.getName());
logFeature.sendLog(guild, LogType.ROLE_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Renamed")
.setDescription(new DescriptionBuilder("Role Renamed")
.appendLine("Role: %s".formatted(event.getRole().getAsMention()), true)
.appendLine("Name: `%s` -> `%s`".formatted(oldName, newName), true)
.build())
@ -113,7 +113,7 @@ public class RoleListener implements EventListener {
log.info("Role \"{}\" color was updated in guild \"{}\"", event.getRole().getName(), guild.getName());
logFeature.sendLog(guild, LogType.ROLE_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Color Updated")
.setDescription(new DescriptionBuilder("Role Color Updated")
.appendLine("Role: %s".formatted(event.getRole().getAsMention()), true)
.appendLine("Color: %s -> %s".formatted(
RoleUtils.getFormattedColor(oldColor),
@ -130,7 +130,7 @@ public class RoleListener implements EventListener {
log.info("Role \"{}\" icon was updated in guild \"{}\"", event.getRole().getName(), guild.getName());
logFeature.sendLog(guild, LogType.ROLE_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Icon Updated")
.setDescription(new DescriptionBuilder("Role Icon Updated")
.appendLine("Role: %s".formatted(event.getRole().getAsMention()), true)
.appendLine("Icon: `%s` -> `%s`".formatted(
oldIcon == null ? "None" : oldIcon.getEmoji(),
@ -144,7 +144,7 @@ public class RoleListener implements EventListener {
public void onRoleUpdateHoisted(@NonNull BatGuild guild, @NonNull Role role, boolean wasHoisted, boolean isHoisted, @NonNull RoleUpdateHoistedEvent event) {
log.info("Role \"{}\" hoisted status was updated in guild \"{}\"", role.getName(), guild.getName());
logFeature.sendLog(guild, LogType.ROLE_CONFIGURATION, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Role Hoisted Status Updated")
.setDescription(new DescriptionBuilder("Role Hoisted Status Updated")
.appendLine("Role: %s".formatted(role.getAsMention()), true)
.appendLine("Hoisted: `%s` -> `%s`".formatted(
wasHoisted ? "Yes" : "No",

@ -1,6 +1,6 @@
package cc.fascinated.bat.features.logging.listeners;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.logging.LogFeature;
@ -32,7 +32,7 @@ public class StickerListener implements EventListener {
public void onGuildStickerAdd(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerAddedEvent event) {
log.info("Sticker \"{}\" was added in guild \"{}\"", sticker.getName(), guild.getName());
logFeature.sendLog(guild, LogType.STICKER_ADD, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Sticker Added")
.setDescription(new DescriptionBuilder("Sticker Added")
.appendLine("Sticker: %s".formatted(sticker.getName()), true)
.appendLine("URL: %s".formatted(sticker.getIconUrl()), true)
.build())
@ -43,7 +43,7 @@ public class StickerListener implements EventListener {
public void onGuildStickerRemove(@NonNull BatGuild guild, @NonNull GuildSticker sticker, @NonNull GuildStickerRemovedEvent event) {
log.info("Sticker \"{}\" was removed in guild \"{}\"", sticker.getName(), guild.getName());
logFeature.sendLog(guild, LogType.STICKER_REMOVE, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Sticker Removed")
.setDescription(new DescriptionBuilder("Sticker Removed")
.appendLine("Sticker: %s".formatted(sticker.getName()), true)
.appendLine("URL: %s".formatted(sticker.getIconUrl()), true)
.build())
@ -55,7 +55,7 @@ public class StickerListener implements EventListener {
@NonNull String newName, @NonNull GuildStickerUpdateNameEvent event) {
log.info("Sticker \"{}\" was renamed in guild \"{}\" from \"{}\" to \"{}\"", sticker.getName(), guild.getName(), oldName, newName);
logFeature.sendLog(guild, LogType.STICKER_NAME_UPDATED, EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("Sticker Renamed")
.setDescription(new DescriptionBuilder("Sticker Renamed")
.appendLine("Sticker: %s".formatted(sticker.getName()), true)
.appendLine("Name: `%s` -> `%s`".formatted(oldName, newName), true)
.build())

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.messagesnipe.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.PasteUtils;
import cc.fascinated.bat.features.messagesnipe.MessageSnipeFeature;
@ -36,7 +36,7 @@ public class DeletedSubCommand extends BatCommand {
String content = message.getMessage().getContentStripped();
String formattedContent = content.length() > 512 ? PasteUtils.uploadPaste(content).getUrl() : "```\n%s\n```".formatted(content);
event.replyEmbeds(EmbedUtils.genericEmbed()
.setDescription(new EmbedDescriptionBuilder("Deleted Message Snipe")
.setDescription(new DescriptionBuilder("Deleted Message Snipe")
.appendLine("Author: **%s** (%s)".formatted(author.getAsMention(), author.getId()), true)
.appendLine("Deleted: <t:%d:R>".formatted(message.getDeletedDate().getTime() / 1000), true)
.appendLine("Content: %s".formatted(formattedContent), true)

@ -86,7 +86,7 @@ public class MinecraftProfile extends Serializable {
}
server.setLastState(isOnline);
EmbedBuilder embedBuilder = isOnline ? EmbedUtils.successEmbed() : EmbedUtils.errorEmbed();
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Server Watcher");
DescriptionBuilder description = new DescriptionBuilder("Server Watcher");
description.appendLine("%s %s server `%s` is now **%s**".formatted(
isOnline ? Emojis.CHECK_MARK_EMOJI : Emojis.CROSS_MARK_EMOJI,
EnumUtils.getEnumName(server.getPlatform()),

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.minecraft.command.minecraft;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
@ -46,7 +46,7 @@ public class LookupPlayerSubCommand extends BatCommand {
}
String headUrl = cachedPlayer.getSkin() != null ? cachedPlayer.getSkin().getParts().get(Skin.SkinPart.HEAD.getName()) : null;
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Player Lookup")
DescriptionBuilder description = new DescriptionBuilder("Player Lookup")
.appendLine("Username: `%s`".formatted(cachedPlayer.getUsername()), true)
.appendLine("UUID: `%s`".formatted(cachedPlayer.getUniqueId().toString()), true);
if (cachedPlayer.getSkin() != null) {

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.minecraft.command.minecraft;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
@ -62,7 +62,7 @@ public class LookupServerSubCommand extends BatCommand {
int platformDefaultPort = platform.equalsIgnoreCase("java") ? 25565 : 19132;
String hostname = server.getHostname() + (server.getPort() != platformDefaultPort ? ":" + server.getPort() : "");
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Server Lookup [(Raw Data)](%s)".formatted(
DescriptionBuilder description = new DescriptionBuilder("Server Lookup [(Raw Data)](%s)".formatted(
"https://api.mcutils.xyz/server/%s/%s".formatted(platform, hostname)
));
description.appendLine("Host: `%s`".formatted(hostname), true);

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.minecraft.command.serverwatcher;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.EnumUtils;
import cc.fascinated.bat.features.minecraft.MinecraftProfile;
@ -38,7 +38,7 @@ public class ListSubCommand extends BatCommand {
for (ServerWatcher server : profile.getServerWatchers()) {
watchers.computeIfAbsent(server.getPlatform(), k -> new ArrayList<>()).add(server);
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Server Watcher");
DescriptionBuilder description = new DescriptionBuilder("Server Watcher");
description.appendLine("Here is a list of all the servers being watched", false);
description.emptyLine();

@ -1,23 +1,111 @@
package cc.fascinated.bat.features.moderation;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.Feature;
import cc.fascinated.bat.features.FeatureProfile;
import cc.fascinated.bat.features.moderation.command.PurgeCommand;
import cc.fascinated.bat.features.moderation.command.*;
import cc.fascinated.bat.features.moderation.punish.Punishment;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.CommandService;
import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.GuildService;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.unions.ChannelUnion;
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.List;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class ModerationFeature extends Feature {
@Log4j2
public class ModerationFeature extends Feature implements EventListener {
private final GuildService guildService;
private final UserService userService;
@Autowired
public ModerationFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
public ModerationFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService,
@NonNull UserService userService) {
super("Moderation", FeatureProfile.FeatureState.DISABLED, true);
this.guildService = guildService;
this.userService = userService;
super.registerCommand(commandService, context.getBean(PurgeCommand.class));
super.registerCommand(commandService, context.getBean(WarnCommand.class));
super.registerCommand(commandService, context.getBean(KickCommand.class));
super.registerCommand(commandService, context.getBean(BanCommand.class));
super.registerCommand(commandService, context.getBean(MuteCommand.class));
super.registerCommand(commandService, context.getBean(UnbanCommand.class));
super.registerCommand(commandService, context.getBean(UnmuteCommand.class));
super.registerCommand(commandService, context.getBean(PunishHistoryCommand.class));
}
/**
* This method is called every minute to check
* the status of temporary punishments
*/
@Scheduled(cron = "0 * * * * *")
public void checkTemporaryPunishments() {
for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled
continue;
}
PunishmentProfile profile = batGuild.getPunishmentProfile();
for (Map.Entry<String, List<Punishment>> entry : profile.getPunishments().entrySet()) {
BatUser user = userService.getUser(entry.getKey());
if (user == null) { // This should never happen
continue;
}
for (Punishment punishment : entry.getValue()) {
// Check if the punishment is not temporary and has not expired
if (punishment.getEndDate() == null
|| punishment.isExpired()
|| punishment.getEndDate().toInstant().isAfter(Instant.now())) {
continue;
}
BatUser issuer = userService.getUser(punishment.getIssuer());
punishment.setExpired(true);
profile.removePunishment(
user,
issuer,
batGuild,
punishment.getType(),
punishment.getReason()
);
log.info("Expired punishment for user {}", user.getDiscordUser().getEffectiveName());
}
}
}
}
@Override
public void onChannelCreate(@NonNull BatGuild guild, @NonNull ChannelCreateEvent event) {
if (guild.getFeatureProfile().isFeatureDisabled(this)) { // Check if the feature is disabled
return;
}
PunishmentProfile profile = guild.getPunishmentProfile();
ChannelUnion channelUnion = event.getChannel();
if (channelUnion.getType() != ChannelType.TEXT) {
return;
}
profile.upsetMutedRolePermissions(guild, channelUnion.asGuildChannel());
}
}

@ -0,0 +1,75 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TimeUtils;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "ban",
description = "Bans a member",
requiredPermissions = Permission.BAN_MEMBERS
)
public class BanCommand extends BatCommand {
private final UserService userService;
@Autowired
public BanCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to ban", true),
new OptionData(OptionType.STRING, "reason", "The reason for the ban", false),
new OptionData(OptionType.STRING, "length", "The length of the ban", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
OptionMapping lengthOption = event.getOption("length");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
String reason = reasonOption == null ? null : reasonOption.getAsString();
long length = lengthOption == null ? -1 : TimeUtils.fromString(lengthOption.getAsString());
if (lengthOption != null && length == -1) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Invalid length provided")
.build()
).queue();
return;
}
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot ban this user")
.build()
).queue();
return;
}
profile.temporaryPunish(targetUser, user, guild, PunishmentType.BAN, reason, length);
profile.punishmentResponse(event, PunishmentType.BAN, targetUser, reason, false, length);
}
}

@ -0,0 +1,65 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.RoleUtils;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "kick",
description = "Kicks a member",
requiredPermissions = Permission.KICK_MEMBERS
)
public class KickCommand extends BatCommand {
private final UserService userService;
@Autowired
public KickCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to kick", true),
new OptionData(OptionType.STRING, "reason", "The reason for the kick", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
assert event.getGuild() != null;
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
String reason = reasonOption == null ? null : reasonOption.getAsString();
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot kick this user")
.build()
).queue();
return;
}
profile.punishMember(targetUser, user, guild, PunishmentType.KICK, reason);
profile.punishmentResponse(event, PunishmentType.BAN, targetUser, reason, false, -1);
}
}

@ -0,0 +1,75 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TimeUtils;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "mute",
description = "Mutes a member",
requiredPermissions = Permission.MANAGE_CHANNEL
)
public class MuteCommand extends BatCommand {
private final UserService userService;
@Autowired
public MuteCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to mute", true),
new OptionData(OptionType.STRING, "reason", "The reason for the mute", false),
new OptionData(OptionType.STRING, "length", "The length of the mute", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
OptionMapping lengthOption = event.getOption("length");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
String reason = reasonOption == null ? null : reasonOption.getAsString();
long length = lengthOption == null ? -1 : TimeUtils.fromString(lengthOption.getAsString());
if (lengthOption != null && length == -1) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Invalid length provided")
.build()
).queue();
return;
}
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot mute this user")
.build()
).queue();
return;
}
profile.temporaryPunish(targetUser, user, guild, PunishmentType.MUTE, reason, length);
profile.punishmentResponse(event, PunishmentType.MUTE, targetUser, reason, false, length);
}
}

@ -0,0 +1,102 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.moderation.punish.Punishment;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import com.github.ygimenez.method.Pages;
import com.github.ygimenez.model.InteractPage;
import com.github.ygimenez.model.Page;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
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.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;
import java.util.ArrayList;
import java.util.List;
/**
* @author Fascinated (fascinated7)
*/
@Component
@CommandInfo(
name = "punish-history",
description = "View the punishment history of a member",
requiredPermissions = Permission.MANAGE_SERVER
)
public class PunishHistoryCommand extends BatCommand {
private static final int PUNISHMENTS_PER_PAGE = 10;
private final UserService userService;
@Autowired
public PunishHistoryCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to view the punishment history of", true)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
PunishmentProfile profile = guild.getPunishmentProfile();
List<Punishment> punishments = profile.getPunishments(targetUser);
List<Page> pages = new ArrayList<>();
int totalPages = (int) Math.ceil((double) punishments.size() / PUNISHMENTS_PER_PAGE);
if (totalPages >= 1) {
for (int i = 0; i < totalPages; i++) {
int fromIndex = i * PUNISHMENTS_PER_PAGE;
int toIndex = Math.min(fromIndex + PUNISHMENTS_PER_PAGE, punishments.size());
List<Punishment> subList = punishments.subList(fromIndex, toIndex);
DescriptionBuilder history = new DescriptionBuilder("Punishment history for %s".formatted(targetUser.getDiscordUser().getAsMention()));
for (Punishment punishment : subList) {
BatUser issuer = userService.getUser(punishment.getIssuer());
history.appendLine("> **[%s%s]** <t:%s:R> Reason: `%s` | Issuer: %s%s".formatted(
punishment.getEndDate() != null ? "TEMP-" : "",
punishment.getType().name(),
punishment.getIssuedDate().toInstant().getEpochSecond(),
punishment.getReason() == null ? "N/A" : punishment.getReason(),
issuer.getDiscordUser().getAsMention(),
punishment.isExpired() ? " **(Expired)**" : ""
), false);
}
pages.add(InteractPage.of(EmbedUtils.successEmbed()
.setDescription(history.build())
.setFooter("Page %d/%d".formatted(i + 1, totalPages))
.build()));
}
} else {
pages.add(InteractPage.of(EmbedUtils.successEmbed()
.setDescription("No punishments found for %s".formatted(targetUser.getDiscordUser().getAsMention()))
.build()));
}
event.replyEmbeds((MessageEmbed) pages.get(0).getContent()).queue(success -> {
if (pages.size() < 2) { // If there is only one page, we don't need to paginate
return;
}
success.retrieveOriginal().queue(message -> {
Pages.paginate(message, pages, true, (interactedUser) -> interactedUser.getIdLong() == user.getDiscordUser().getIdLong());
});
});
}
}

@ -3,7 +3,11 @@ package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.Category;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.features.logging.LogFeature;
import cc.fascinated.bat.features.logging.LogType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import lombok.NonNull;
@ -16,6 +20,9 @@ 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@ -28,8 +35,11 @@ import java.util.concurrent.TimeUnit;
@CommandInfo(name = "purge", description = "Purge messages from a channel", requiredPermissions = Permission.MESSAGE_MANAGE, category = Category.MODERATION)
public class PurgeCommand extends BatCommand {
private final long MESSAGE_DELETE_DELAY = TimeUnit.SECONDS.toMillis(10);
private final LogFeature logFeature;
public PurgeCommand() {
@Autowired
public PurgeCommand(@NonNull LogFeature logFeature) {
this.logFeature = logFeature;
super.addOptions(new OptionData(OptionType.INTEGER, "amount", "The amount of messages to remove", true));
}
@ -55,12 +65,21 @@ public class PurgeCommand extends BatCommand {
then.retrieveOriginal().queue(original -> {
if (original == null) return;
List<Message> toRemove = messages.stream().filter(message -> !original.getId().equals(message.getId())).toList();
textChannel.deleteMessages(toRemove).queue(done -> then.editOriginalEmbeds(EmbedUtils.successEmbed()
textChannel.deleteMessages(toRemove).queue(done -> {
then.editOriginalEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully purged `%s` messages\n\n*This message will be removed <t:%s:R>*".formatted(
amount,
(System.currentTimeMillis() + MESSAGE_DELETE_DELAY) / 1000
))
.build()).queue(message -> message.delete().queueAfter(MESSAGE_DELETE_DELAY, TimeUnit.MILLISECONDS)));
.build()).queue(message -> message.delete().queueAfter(MESSAGE_DELETE_DELAY, TimeUnit.MILLISECONDS));
logFeature.sendLog(guild, LogType.PURGE, EmbedUtils.successEmbed()
.setDescription(new DescriptionBuilder("Purged Messages")
.appendLine("Amount: `%s`".formatted(NumberFormatter.simpleFormat(amount)), true)
.appendLine("Channel: %s".formatted(textChannel.getAsMention()), true)
.appendLine("Issuer: %s".formatted(member.getAsMention()), true)
.build())
.build());
});
});
});
});

@ -0,0 +1,72 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.moderation.punish.Punishment;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "unban",
description = "Un-bans a member",
requiredPermissions = Permission.BAN_MEMBERS
)
public class UnbanCommand extends BatCommand {
private final UserService userService;
@Autowired
public UnbanCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.STRING, "member", "The ID of the member you want to un-ban", true),
new OptionData(OptionType.STRING, "reason", "The reason for the un-ban", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsString());
String reason = reasonOption == null ? null : reasonOption.getAsString();
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot unban this user")
.build()
).queue();
return;
}
Punishment punishment = profile.removePunishment(targetUser, user, guild, PunishmentType.BAN, reason);
if (punishment == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("This user is not banned")
.build()
).queue();
return;
}
profile.punishmentResponse(event, PunishmentType.BAN, targetUser, reason, true, -1);
}
}

@ -0,0 +1,72 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.moderation.punish.Punishment;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "unmute",
description = "Un-mutes a member",
requiredPermissions = Permission.MANAGE_CHANNEL
)
public class UnmuteCommand extends BatCommand {
private final UserService userService;
@Autowired
public UnmuteCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to un-mute", true),
new OptionData(OptionType.STRING, "reason", "The reason for the un-mute", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
String reason = reasonOption == null ? null : reasonOption.getAsString();
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot unmute this user")
.build()
).queue();
return;
}
Punishment punishment = profile.removePunishment(targetUser, user, guild, PunishmentType.MUTE, reason);
if (punishment == null) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("This user is not muted")
.build()
).queue();
return;
}
profile.punishmentResponse(event, PunishmentType.MUTE, targetUser, reason, true, -1);
}
}

@ -0,0 +1,63 @@
package cc.fascinated.bat.features.moderation.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
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
@CommandInfo(
name = "warn",
description = "Warns a member",
requiredPermissions = Permission.MANAGE_CHANNEL
)
public class WarnCommand extends BatCommand {
private final UserService userService;
@Autowired
public WarnCommand(@NonNull UserService userService) {
this.userService = userService;
super.addOptions(
new OptionData(OptionType.USER, "member", "The member you want to warn", true),
new OptionData(OptionType.STRING, "reason", "The reason for the warning", false)
);
}
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
OptionMapping memberOption = event.getOption("member");
OptionMapping reasonOption = event.getOption("reason");
assert memberOption != null;
BatUser targetUser = userService.getUser(memberOption.getAsUser().getId());
String reason = reasonOption == null ? null : reasonOption.getAsString();
PunishmentProfile profile = guild.getPunishmentProfile();
if (!profile.canPunish(guild, user, targetUser)) {
event.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("You cannot warn this user")
.build()
).queue();
return;
}
profile.punishMember(targetUser, user, guild, PunishmentType.WARN, reason);
profile.punishmentResponse(event, PunishmentType.WARN, targetUser, reason, false, -1);
}
}

@ -0,0 +1,45 @@
package cc.fascinated.bat.features.moderation.punish;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
@Setter
public class Punishment {
/**
* The type of punishment
*/
private final PunishmentType type;
/**
* The reason for the punishment
*/
private final String reason;
/**
* The member who issued out this punishment
*/
private final String issuer;
/**
* The date the punishment was issued
*/
private final Date issuedDate;
/**
* The time the punishment will end, if temporary
*/
private Date endDate;
/**
* If the punishment has expired
*/
private boolean expired;
}

@ -0,0 +1,395 @@
package cc.fascinated.bat.features.moderation.punish;
import cc.fascinated.bat.common.*;
import cc.fascinated.bat.features.logging.LogFeature;
import cc.fascinated.bat.features.logging.LogType;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import com.google.gson.Gson;
import lombok.Getter;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.PermissionOverride;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.bson.Document;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author Fascinated (fascinated7)
*/
@Getter
public class PunishmentProfile extends Serializable {
/**
* The punishments issued in this guild
*/
private final Map<String, List<Punishment>> punishments = new HashMap<>();
/**
* Punish a member
*
* @param user The user to punish
* @param issuer The issuer of the punishment
* @param guild The guild the punishment is being issued in
* @param type The type of punishment
* @param reasonString The reason for the punishment
* @param length The length of the punishment
* @return The punishment issued
*/
private Punishment punishMember(BatUser user, BatUser issuer, BatGuild guild, PunishmentType type, String reasonString, long length) {
Punishment punishment = new Punishment(
type,
reasonString,
issuer.getId(),
new Date(),
length == -1 ? null : new Date(System.currentTimeMillis() + length),
false
);
List<Punishment> userPunishments = this.punishments.getOrDefault(user.getId(), new ArrayList<>());
Punishment previousPunishment = this.getLatestActivePunishment(user, type);
// Expire the previous punishment
if (previousPunishment != null &&
(previousPunishment.getType() == PunishmentType.MUTE || previousPunishment.getType() == PunishmentType.BAN)) {
previousPunishment.setExpired(true);
}
userPunishments.add(punishment);
this.punishments.put(user.getId(), userPunishments);
String reason = reasonString == null ? "No reason provided" : reasonString;
String lengthFormatted = TimeUtils.format(length);
Guild discordGuild = guild.getDiscordGuild();
DescriptionBuilder punishmentDescription = new DescriptionBuilder("%s Issued".formatted(EnumUtils.getEnumName(type)))
.appendLine("User: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Issuer: %s".formatted(issuer.getDiscordUser().getAsMention()), true)
.appendLine("Reason: `%s`".formatted(reason), true);
// Only applicable for Mutes and Bans
if (type == PunishmentType.MUTE || type == PunishmentType.BAN) {
punishmentDescription.appendLine("Length: `%s`".formatted(length == -1 ? "Permanent" : lengthFormatted), true);
}
LogFeature.INSTANCE.sendLog(guild, LogType.PUNISHMENT_ISSUED, EmbedUtils.successEmbed()
.setDescription(punishmentDescription.build())
.build());
String name = EnumUtils.getEnumName(type).endsWith("e") ? EnumUtils.getEnumName(type) + "d" : EnumUtils.getEnumName(type) + "ed";
user.getDiscordUser().openPrivateChannel().queue(channel -> {
DescriptionBuilder descriptionBuilder = new DescriptionBuilder(null);
descriptionBuilder.appendLine("🛡️ You have been **%s** in **%s**".formatted(
name,
discordGuild.getName()
), false);
if (length != -1 && (type == PunishmentType.MUTE || type == PunishmentType.BAN)) {
descriptionBuilder.appendLine("Length: %s".formatted(lengthFormatted), true);
}
descriptionBuilder.appendLine("Reason: %s".formatted(reason), true);
try {
channel.sendMessage(descriptionBuilder.build()).complete();
} catch (Exception ignored) {
} // Ignore since the user has DMs disabled
});
switch (type) {
case BAN -> discordGuild.ban(user.getDiscordUser(), 0, TimeUnit.SECONDS).queue();
case KICK -> discordGuild.kick(user.getDiscordUser()).queue();
case MUTE -> {
this.ensureMutedPermissionsAreSetup(guild); // Ensure the muted role permissions are set up
Role mutedRole = this.findOrCreateMutedRole(guild);
discordGuild.addRoleToMember(user.getDiscordUser(), mutedRole).queue();
}
}
return punishment;
}
/**
* Punish a member
*
* @param user The user to punish
* @param issuer The issuer of the punishment
* @param guild The guild the punishment is being issued in
* @param type The type of punishment
* @param reason The reason for the punishment
* @return The punishment issued
*/
public Punishment punishMember(BatUser user, BatUser issuer, BatGuild guild, PunishmentType type, String reason) {
return this.punishMember(user, issuer, guild, type, reason, -1);
}
/**
* Temporarily punish a member
*
* @param user The user to punish
* @param issuer The issuer of the punishment
* @param guild The guild the punishment is being issued in
* @param type The type of punishment
* @param reason The reason for the punishment
* @param length The length of the punishment
* @return The punishment issued
*/
public Punishment temporaryPunish(BatUser user, BatUser issuer, BatGuild guild, PunishmentType type, String reason, long length) {
return this.punishMember(user, issuer, guild, type, reason, length);
}
/**
* Remove a punishment from a user
*
* @param user The user to remove the punishment from
* @param issuer The issuer of the punishment removal
* @param guild The guild the punishment is being removed from
* @param type The type of punishment
* @param reason The reason for the punishment removal
*/
public Punishment removePunishment(BatUser user, BatUser issuer, BatGuild guild, PunishmentType type, String reason) {
Punishment punishment = this.getLatestActivePunishment(user, type);
if (punishment == null && type != PunishmentType.BAN) {
return null;
}
if (punishment == null) { // In case they are unbanning a user that wasn't banned by the bot
punishment = new Punishment(type, reason, issuer.getId(), new Date(), null, false);
}
switch (type) {
case BAN -> guild.getDiscordGuild().unban(user.getDiscordUser()).queue();
case MUTE -> {
Role mutedRole = this.findOrCreateMutedRole(guild);
guild.getDiscordGuild().removeRoleFromMember(user.getDiscordUser(), mutedRole).queue();
}
}
DescriptionBuilder descriptionBuilder = new DescriptionBuilder("%s Removed".formatted(EnumUtils.getEnumName(type)))
.appendLine("User: %s".formatted(user.getDiscordUser().getAsMention()), true)
.appendLine("Issuer: %s".formatted(issuer.getDiscordUser().getAsMention()), true)
.appendLine("Reason: `%s`".formatted(reason), true);
LogFeature.INSTANCE.sendLog(guild, LogType.PUNISHMENT_REMOVED_OR_EXPIRED, EmbedUtils.successEmbed()
.setDescription(descriptionBuilder.build())
.build());
user.getDiscordUser().openPrivateChannel().queue(channel -> {
DescriptionBuilder descriptionBuilder1 = new DescriptionBuilder(null);
descriptionBuilder1.appendLine("🛡️ Your **%s** in **%s** has been removed".formatted(
EnumUtils.getEnumName(type),
guild.getDiscordGuild().getName()
), false);
try {
channel.sendMessage(descriptionBuilder1.build()).complete();
} catch (Exception ignored) {
} // Ignore since the user has DMs disabled
});
punishment.setExpired(true); // Expire the punishment
return punishment;
}
/**
* Gets the latest punishment for a user
*
* @param user The user to get the latest punishment for
* @param type The type of punishment
* @return The latest punishment
*/
public Punishment getLatestActivePunishment(BatUser user, PunishmentType type) {
List<Punishment> punishments = this.punishments.get(user.getId());
if (punishments == null) {
return null;
}
return punishments.stream()
.filter(punishment -> punishment.getType() == type && !punishment.isExpired())
.max(Comparator.comparing(Punishment::getIssuedDate))
.orElse(null);
}
/**
* Gets the punishments for a user
*
* @param user The user to get the punishments for
* @return The punishments
*/
public List<Punishment> getPunishments(BatUser user) {
return this.punishments.getOrDefault(user.getId(), new ArrayList<>()).stream()
.sorted(Comparator.comparing(Punishment::isExpired).reversed().thenComparing(Punishment::getIssuedDate).reversed())
.toList();
}
/**
* Gets the muted role for the Guild, if it
* doesn't exist the bot will create one
*
* @param guild The guild to get the muted role for
* @return The muted role
*/
public Role findOrCreateMutedRole(BatGuild guild) {
Guild discordGuild = guild.getDiscordGuild();
Role mutedRole = discordGuild.getRolesByName("Muted", true).stream().findFirst().orElse(null);
if (mutedRole == null) {
return discordGuild.createRole()
.setName("Muted")
.complete();
}
return mutedRole;
}
/**
* Sets the muted role permissions for a channel
*
* @param guild the guild to get the muted role from
* @param channel the channel to set the permissions for
*/
public void upsetMutedRolePermissions(BatGuild guild, GuildChannel channel) {
PunishmentProfile profile = guild.getPunishmentProfile();
Role mutedRole = profile.findOrCreateMutedRole(guild);
assert mutedRole != null;
if (channel instanceof TextChannel textChannel) {
textChannel.upsertPermissionOverride(mutedRole)
.deny(Permission.MESSAGE_SEND)
.queue();
return;
}
if (channel instanceof VoiceChannel voiceChannel) {
voiceChannel.upsertPermissionOverride(mutedRole)
.deny(Permission.VOICE_SPEAK)
.queue();
}
}
/**
* Ensures that the muted role permissions are
* set up for all channels
*
* @param guild the guild to check
*/
public void ensureMutedPermissionsAreSetup(BatGuild guild) {
Role mutedRole = this.findOrCreateMutedRole(guild);
for (GuildChannel channel : guild.getDiscordGuild().getChannels()) {
for (PermissionOverride override : channel.getPermissionContainer().getRolePermissionOverrides()) {
Role role = override.getRole();
assert role != null;
if (role.getId().equals(mutedRole.getId())) {
return;
}
}
this.upsetMutedRolePermissions(guild, channel);
}
}
/**
* Inform the user that the punishment was applied
*
* @param event The event to respond to
* @param type The type of punishment
* @param targetUser The user that was punished
* @param reason The reason for the punishment
* @param length The length of the punishment
*/
public void punishmentResponse(SlashCommandInteraction event, PunishmentType type, BatUser targetUser, String reason, boolean remove, long length) {
String name = EnumUtils.getEnumName(type).endsWith("e") ? EnumUtils.getEnumName(type) + "d" : EnumUtils.getEnumName(type) + "ed";
DescriptionBuilder description = new DescriptionBuilder("Successfully %s%s %s".formatted(
remove ? "Un-" : "",
name,
targetUser.getDiscordUser().getAsMention()
));
description.appendLine("Reason: %s".formatted(reason == null ? "No reason provided" : reason), true);
if (length != -1) {
description.appendLine("Length: %s".formatted(TimeUtils.format(length)), true);
}
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(description.build())
.build()
).queue();
}
/**
* Check if a user can be punished
*
* @param guild The guild to check
* @param issuer The issuer of the punishment
* @param targetUser The user to punish
* @return If the user can be punished
*/
public boolean canPunish(BatGuild guild, BatUser issuer, BatUser targetUser) {
Member owner = guild.getDiscordGuild().getOwner();
assert owner != null;
if (targetUser == null) {
return false;
}
// Check if the target user is the owner
if (owner.getId().equals(targetUser.getId())) {
return false;
}
// Check if the target user is the bot
if (targetUser.getId().equals(guild.getDiscordGuild().getSelfMember().getId())) {
return false;
}
// Check if the target user is the same as the issuer
if (targetUser.getId().equals(issuer.getId())) {
return false;
}
// Check if the target user has a higher role than the bot
Member member = guild.getDiscordGuild().getMemberById(targetUser.getId());
assert member != null;
if (RoleUtils.hasHigherRole(member, guild.getDiscordGuild().getSelfMember())) {
return false;
}
return true;
}
@Override
public void load(Document document, Gson gson) {
Document punishmentsDocument = (Document) document.getOrDefault("punishments", new Document());
for (String key : punishmentsDocument.keySet()) {
List<Document> punishmentList = punishmentsDocument.getList(key, Document.class, new ArrayList<>());
List<Punishment> punishments = new ArrayList<>();
for (Document punishmentDocument : punishmentList) {
punishments.add(new Punishment(
PunishmentType.valueOf(punishmentDocument.getString("type")),
punishmentDocument.getString("reason"),
punishmentDocument.getString("issuer"),
punishmentDocument.getDate("issuedDate"),
punishmentDocument.getDate("endDate"),
punishmentDocument.getBoolean("expired", false)
));
}
this.punishments.put(key, punishments);
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
Document punishmentsDocument = new Document();
for (String key : this.punishments.keySet()) {
List<Document> punishmentList = new ArrayList<>();
for (Punishment punishment : this.punishments.get(key)) {
Document punishmentDocument = new Document();
punishmentDocument.append("type", punishment.getType().name());
punishmentDocument.append("reason", punishment.getReason());
punishmentDocument.append("issuer", punishment.getIssuer());
punishmentDocument.append("issuedDate", punishment.getIssuedDate());
punishmentDocument.append("endDate", punishment.getEndDate());
punishmentDocument.append("expired", punishment.isExpired());
punishmentList.add(punishmentDocument);
}
punishmentsDocument.append(key, punishmentList);
}
document.append("punishments", punishmentsDocument);
return document;
}
@Override
public void reset() {
this.punishments.clear();
}
}

@ -0,0 +1,11 @@
package cc.fascinated.bat.features.moderation.punish;
/**
* @author Fascinated (fascinated7)
*/
public enum PunishmentType {
KICK,
BAN,
WARN,
MUTE
}

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.reminder.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.reminder.Reminder;
import cc.fascinated.bat.features.reminder.ReminderProfile;
@ -30,7 +30,7 @@ public class ListSubCommand extends BatCommand {
return;
}
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Active Reminders");
DescriptionBuilder description = new DescriptionBuilder("Active Reminders");
for (Reminder reminder : profile.getReminders(user.getDiscordUser())) {
description.appendLine("%s - <t:%s:R> %s".formatted(
reminder.getReminder(),

@ -122,7 +122,7 @@ public class ScoreSaberCommand extends BatCommand {
String timeTaken = TimeUtils.format(System.currentTimeMillis() - before, TimeUtils.BatTimeFormat.FIT, true);
message.editOriginalEmbeds(EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("%s's ScoreSaber Account".formatted(user.getDiscordUser().getAsMention()))
.setDescription(new DescriptionBuilder("%s's ScoreSaber Account".formatted(user.getDiscordUser().getAsMention()))
.appendLine("Name: `%s`".formatted(name), true)
.appendLine("Country: `%s`".formatted(country), true)
.appendLine("Rank: `#%s`".formatted(rank), true)

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.statschannel.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
import cc.fascinated.bat.model.BatGuild;
@ -66,7 +66,7 @@ public class AddSubCommand extends BatCommand {
profile.refreshChannels(guild); // For the stat channels to be updated
event.replyEmbeds(EmbedUtils.successEmbed()
.setDescription(new EmbedDescriptionBuilder("%s The stats channel %s has been created".formatted(
.setDescription(new DescriptionBuilder("%s The stats channel %s has been created".formatted(
Emojis.CHECK_MARK_EMOJI,
voiceChannel.getAsMention()
))

@ -2,7 +2,7 @@ package cc.fascinated.bat.features.statschannel.command;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.statschannel.StatsChannel;
import cc.fascinated.bat.features.statschannel.StatsChannelFeature;
@ -30,7 +30,7 @@ public class CurrentSubCommand extends BatCommand {
@Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
StatsChannelProfile profile = guild.getStatsChannelProfile();
EmbedDescriptionBuilder builder = new EmbedDescriptionBuilder("Stats Channels");
DescriptionBuilder builder = new DescriptionBuilder("Stats Channels");
if (profile.getChannels().isEmpty()) {
builder.appendLine("You have no stats channels set up", true);
} else {

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.welcomer.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.HexColorUtils;
import cc.fascinated.bat.features.welcomer.WelcomerProfile;
@ -74,7 +74,7 @@ public class EmbedSubCommand extends BatCommand {
boolean isMessageEnabled = profile.isMessage();
profile.setEmbed(title, description, color, pingBeforeSend);
EmbedDescriptionBuilder successDescription = new EmbedDescriptionBuilder("Welcomer Embed")
DescriptionBuilder successDescription = new DescriptionBuilder("Welcomer Embed")
.appendLine("%s Successfully set the welcomer embed!".formatted(Emojis.CHECK_MARK_EMOJI), false);
if (isMessageEnabled) {
successDescription.appendLine("*This has removed the plain message welcomer*", false);

@ -3,7 +3,7 @@ package cc.fascinated.bat.features.welcomer.command;
import cc.fascinated.bat.Emojis;
import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
import cc.fascinated.bat.common.DescriptionBuilder;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.welcomer.WelcomerPlaceholders;
import cc.fascinated.bat.features.welcomer.WelcomerProfile;
@ -39,7 +39,7 @@ public class MessageSubCommand extends BatCommand {
boolean isEmbedEnabled = profile.isEmbed();
profile.setMessage(message);
EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Welcomer Message")
DescriptionBuilder description = new DescriptionBuilder("Welcomer Message")
.appendLine("%s Set the message to `%s`".formatted(Emojis.CHECK_MARK_EMOJI, message), false);
if (isEmbedEnabled) {
description.appendLine("*This has removed the embed welcomer*", false);

@ -9,6 +9,7 @@ import cc.fascinated.bat.features.counter.CounterProfile;
import cc.fascinated.bat.features.leveling.LevelingProfile;
import cc.fascinated.bat.features.logging.LogProfile;
import cc.fascinated.bat.features.minecraft.MinecraftProfile;
import cc.fascinated.bat.features.moderation.punish.PunishmentProfile;
import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile;
import cc.fascinated.bat.features.reminder.ReminderProfile;
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
@ -190,6 +191,15 @@ public class BatGuild extends ProfileHolder {
return getProfile(StatsChannelProfile.class);
}
/**
* Gets the punishment profile
*
* @return the punishment profile
*/
public PunishmentProfile getPunishmentProfile() {
return getProfile(PunishmentProfile.class);
}
/**
* Saves the user
*/

@ -4,6 +4,8 @@ import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.common.TimerUtils;
import cc.fascinated.bat.config.Config;
import cc.fascinated.bat.event.EventListener;
import com.github.ygimenez.method.Pages;
import com.github.ygimenez.model.PaginatorBuilder;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
@ -60,6 +62,7 @@ public class DiscordService implements EventListener {
CacheFlag.ONLINE_STATUS
).build()
.awaitReady();
Pages.activate(PaginatorBuilder.createSimplePaginator(JDA)); // Activate the paginator
log.info("Connected to Discord as {}", JDA.getSelfUser().getEffectiveName());
TimerUtils.scheduleRepeating(this::updateActivity, 0, 1000 * 60 * 2);
}