big ass refactor to handle loading guilds and users without spring to make it more futureproof

This commit is contained in:
Lee 2024-07-01 01:12:32 +01:00
parent f566c3bcb5
commit d372c41c98
58 changed files with 755 additions and 638 deletions

@ -1,25 +0,0 @@
package cc.fascinated.bat.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public abstract class Profile {
/**
* The key of the profile.
*/
private String profileKey;
/**
* Resets the profile
*/
public abstract void reset();
}

@ -1,6 +1,9 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import cc.fascinated.bat.BatApplication;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows;
import org.bson.Document;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -9,11 +12,11 @@ import java.util.Map;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Getter
public class ProfileHolder { public abstract class ProfileHolder {
/** /**
* The profiles for the holder * The profiles for the holder
*/ */
private Map<String, Profile> profiles; private final Map<String, Serializable> profiles = new HashMap<>();
/** /**
* Gets a profile for the holder * Gets a profile for the holder
@ -22,19 +25,25 @@ public class ProfileHolder {
* @param <T> The type of the profile * @param <T> The type of the profile
* @return The profile * @return The profile
*/ */
public <T extends Profile> T getProfile(Class<T> clazz) { public abstract <T extends Serializable> T getProfile(Class<T> clazz);
if (profiles == null) {
profiles = new HashMap<>();
}
Profile profile = profiles.values().stream().filter(clazz::isInstance).findFirst().orElse(null); /**
* Gets the profiles for the holder
* using the provided document
*
* @return the profiles
*/
@SneakyThrows
protected <T extends Serializable> T getProfileFromDocument(Class<T> clazz, Document document) {
Serializable profile = getProfiles().get(clazz.getSimpleName());
if (profile == null) { if (profile == null) {
try { T newProfile = clazz.cast(clazz.getDeclaredConstructors()[0].newInstance());
profile = clazz.newInstance(); org.bson.Document profiles = document.get("profiles", new org.bson.Document());
profiles.put(profile.getProfileKey(), profile); org.bson.Document profileDocument = (org.bson.Document) profiles.getOrDefault(clazz.getSimpleName(), new org.bson.Document());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace(); newProfile.load(profileDocument, BatApplication.GSON);
} getProfiles().put(clazz.getSimpleName(), newProfile);
return newProfile;
} }
return clazz.cast(profile); return clazz.cast(profile);
} }

@ -0,0 +1,36 @@
package cc.fascinated.bat.common;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bson.Document;
/**
* @author Fascinated (fascinated7)
*/
@Setter
@Getter
@NoArgsConstructor
public abstract class Serializable {
/**
* Load data from the provided document into this profile
*
* @param document the document to load data from
* @param gson the GSON instance to use
*/
public abstract void load(Document document, Gson gson);
/**
* Serialize this profile into a Bson document
*
* @param gson the GSON instance to use
* @return the serialized document
*/
public abstract Document serialize(Gson gson);
/**
* Resets the profile to its default state
*/
public abstract void reset();
}

@ -4,10 +4,8 @@ import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.afk.profile.AfkProfile; import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -15,13 +13,6 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class AfkReturnListener implements EventListener { public class AfkReturnListener implements EventListener {
private final GuildService guildService;
@Autowired
public AfkReturnListener(@NonNull GuildService guildService) {
this.guildService = guildService;
}
@Override @Override
public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) { public void onGuildMessageReceive(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull MessageReceivedEvent event) {
AfkProfile profile = guild.getProfile(AfkProfile.class); AfkProfile profile = guild.getProfile(AfkProfile.class);
@ -29,7 +20,6 @@ public class AfkReturnListener implements EventListener {
return; return;
} }
profile.removeAfkUser(guild, user.getId()); profile.removeAfkUser(guild, user.getId());
guildService.saveGuild(guild);
event.getMessage().reply("Welcome back, %s! You are no longer AFK.".formatted(user.getDiscordUser().getAsMention())).queue(); event.getMessage().reply("Welcome back, %s! You are no longer AFK.".formatted(user.getDiscordUser().getAsMention())).queue();
} }
} }

@ -6,14 +6,12 @@ import cc.fascinated.bat.common.MemberUtils;
import cc.fascinated.bat.features.afk.profile.AfkProfile; import cc.fascinated.bat.features.afk.profile.AfkProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -22,11 +20,7 @@ import org.springframework.stereotype.Component;
@Component @Component
@CommandInfo(name = "afk", description = "Sets your AFK status") @CommandInfo(name = "afk", description = "Sets your AFK status")
public class AfkCommand extends BatCommand { public class AfkCommand extends BatCommand {
private final GuildService guildService; public AfkCommand() {
@Autowired
public AfkCommand(@NonNull GuildService guildService) {
this.guildService = guildService;
super.addOption(OptionType.STRING, "reason", "The reason for being AFK", false); super.addOption(OptionType.STRING, "reason", "The reason for being AFK", false);
} }
@ -40,7 +34,6 @@ public class AfkCommand extends BatCommand {
} }
profile.addAfkUser(guild, member.getId(), reason); profile.addAfkUser(guild, member.getId(), reason);
guildService.saveGuild(guild);
interaction.reply("You are now AFK: %s%s".formatted( interaction.reply("You are now AFK: %s%s".formatted(
profile.getAfkReason(member.getId()), profile.getAfkReason(member.getId()),
MemberUtils.hasPermissionToEdit(guild, user) ? "" : MemberUtils.hasPermissionToEdit(guild, user) ? "" :

@ -1,9 +1,12 @@
package cc.fascinated.bat.features.afk.profile; package cc.fascinated.bat.features.afk.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import com.google.gson.Gson;
import lombok.NoArgsConstructor;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import org.bson.Document;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.HashMap;
@ -13,7 +16,8 @@ import java.util.Map;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Component @Component
public class AfkProfile extends Profile { @NoArgsConstructor
public class AfkProfile extends Serializable {
private static final String DEFAULT_REASON = "Away"; private static final String DEFAULT_REASON = "Away";
/** /**
@ -21,10 +25,6 @@ public class AfkProfile extends Profile {
*/ */
private Map<String, String> afkUsers; private Map<String, String> afkUsers;
public AfkProfile() {
super("afk");
}
/** /**
* Adds a user to the AFK list * Adds a user to the AFK list
* *
@ -102,4 +102,21 @@ public class AfkProfile extends Profile {
public void reset() { public void reset() {
afkUsers = new HashMap<>(); afkUsers = new HashMap<>();
} }
@Override
public void load(Document document, Gson gson) {
afkUsers = new HashMap<>();
for (String key : document.keySet()) {
afkUsers.put(key, document.getString(key));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : afkUsers.keySet()) {
document.put(key, afkUsers.get(key));
}
return document;
}
} }

@ -7,7 +7,6 @@ import cc.fascinated.bat.common.RoleUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
@ -24,12 +23,9 @@ import org.springframework.stereotype.Component;
@Component("autoroles:add.sub") @Component("autoroles:add.sub")
@CommandInfo(name = "add", description = "Adds a role to the auto roles list") @CommandInfo(name = "add", description = "Adds a role to the auto roles list")
public class AddSubCommand extends BatSubCommand { public class AddSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public AddSubCommand(@NonNull GuildService guildService) { public AddSubCommand() {
super.addOption(OptionType.ROLE, "role", "The role to add", true); super.addOption(OptionType.ROLE, "role", "The role to add", true);
this.guildService = guildService;
} }
@Override @Override
@ -80,7 +76,6 @@ public class AddSubCommand extends BatSubCommand {
// Add the role to the auto roles list // Add the role to the auto roles list
profile.addRole(role.getId()); profile.addRole(role.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have added %s to the auto roles list".formatted(role.getAsMention())) .setDescription("You have added %s to the auto roles list".formatted(role.getAsMention()))
.build()).queue(); .build()).queue();

@ -6,12 +6,10 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -20,19 +18,11 @@ import org.springframework.stereotype.Component;
@Component("autoroles:clear.sub") @Component("autoroles:clear.sub")
@CommandInfo(name = "clear", description = "Clears all auto roles") @CommandInfo(name = "clear", description = "Clears all auto roles")
public class ClearSubCommand extends BatSubCommand { public class ClearSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public ClearSubCommand(GuildService guildService) {
this.guildService = guildService;
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class); AutoRoleProfile profile = guild.getProfile(AutoRoleProfile.class);
profile.reset(); profile.reset();
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully cleared all auto roles") .setDescription("Successfully cleared all auto roles")
.build()).queue(); .build()).queue();

@ -6,7 +6,6 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile; import cc.fascinated.bat.features.autorole.profile.AutoRoleProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
@ -23,12 +22,9 @@ import org.springframework.stereotype.Component;
@Component("autoroles:remove.sub") @Component("autoroles:remove.sub")
@CommandInfo(name = "remove", description = "Removes a role from the auto roles list") @CommandInfo(name = "remove", description = "Removes a role from the auto roles list")
public class RemoveSubCommand extends BatSubCommand { public class RemoveSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public RemoveSubCommand(GuildService guildService) { public RemoveSubCommand() {
super.addOption(OptionType.ROLE, "role", "The role to remove", true); super.addOption(OptionType.ROLE, "role", "The role to remove", true);
this.guildService = guildService;
} }
@Override @Override
@ -51,7 +47,6 @@ public class RemoveSubCommand extends BatSubCommand {
} }
profile.removeRole(role.getId()); profile.removeRole(role.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully removed the role %s from the auto roles list".formatted(role.getAsMention())) .setDescription("Successfully removed the role %s from the auto roles list".formatted(role.getAsMention()))
.build()).queue(); .build()).queue();

@ -1,11 +1,14 @@
package cc.fascinated.bat.features.autorole.profile; package cc.fascinated.bat.features.autorole.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import org.bson.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -15,7 +18,8 @@ import java.util.List;
*/ */
@Setter @Setter
@Getter @Getter
public class AutoRoleProfile extends Profile { @NoArgsConstructor
public class AutoRoleProfile extends Serializable {
private static final int DEFAULT_MAX_ROLES = 10; private static final int DEFAULT_MAX_ROLES = 10;
private static final int PREMIUM_MAX_ROLES = 25; private static final int PREMIUM_MAX_ROLES = 25;
@ -24,10 +28,6 @@ public class AutoRoleProfile extends Profile {
*/ */
private List<String> roleIds; private List<String> roleIds;
public AutoRoleProfile() {
super("auto-role");
}
/** /**
* Gets the maximum amount of roles that can be set in the guild * Gets the maximum amount of roles that can be set in the guild
* *
@ -35,7 +35,7 @@ public class AutoRoleProfile extends Profile {
* @return the amount of role slots * @return the amount of role slots
*/ */
public static int getMaxRoleSlots(BatGuild guild) { public static int getMaxRoleSlots(BatGuild guild) {
if (guild.getPremium().hasPremium()) { if (guild.getPremiumProfile().hasPremium()) {
return PREMIUM_MAX_ROLES; return PREMIUM_MAX_ROLES;
} }
return DEFAULT_MAX_ROLES; return DEFAULT_MAX_ROLES;
@ -110,4 +110,16 @@ public class AutoRoleProfile extends Profile {
public void reset() { public void reset() {
roleIds.clear(); roleIds.clear();
} }
@Override
public void load(Document document, Gson gson) {
roleIds = document.getList("roleIds", String.class);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("roleIds", roleIds);
return document;
}
} }

@ -4,6 +4,7 @@ import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@ -41,14 +42,13 @@ public class RemoveSubCommand extends BatSubCommand {
interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue(); interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return; return;
} }
BatGuild.Premium premium = batGuild.getPremium(); PremiumProfile premium = batGuild.getPremiumProfile();
if (!premium.hasPremium()) { if (!premium.hasPremium()) {
interaction.reply("The guild does not have premium").queue(); interaction.reply("The guild does not have premium").queue();
return; return;
} }
premium.removePremium(); premium.removePremium();
guildService.saveGuild(batGuild);
interaction.reply("The guild **%s** has had its premium removed".formatted(guild.getName())).queue(); interaction.reply("The guild **%s** has had its premium removed".formatted(guild.getName())).queue();
} }
} }

@ -4,6 +4,7 @@ import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.CommandInfo; import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@ -23,7 +24,7 @@ public class SetSubCommand extends BatSubCommand {
private final GuildService guildService; private final GuildService guildService;
@Autowired @Autowired
public SetSubCommand(GuildService guildService) { public SetSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService; this.guildService = guildService;
super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true); super.addOption(OptionType.STRING, "guild", "The guild id to set as premium", true);
super.addOption(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true); super.addOption(OptionType.BOOLEAN, "infinite", "Whether the premium length should be infinite", true);
@ -49,13 +50,12 @@ public class SetSubCommand extends BatSubCommand {
interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue(); interaction.reply("The guild with the id %s does not exist".formatted(guildId)).queue();
return; return;
} }
BatGuild.Premium premium = batGuild.getPremium(); PremiumProfile premium = batGuild.getPremiumProfile();
if (!infinite) { if (!infinite) {
premium.addTime(); premium.addTime();
} else { } else {
premium.addInfiniteTime(); premium.addInfiniteTime();
} }
guildService.saveGuild(batGuild);
if (!infinite) { if (!infinite) {
interaction.reply("The guild **%s** has been set as premium until <t:%s>".formatted(guild.getName(), premium.getExpiresAt().toInstant().toEpochMilli() / 1000)).queue(); interaction.reply("The guild **%s** has been set as premium until <t:%s>".formatted(guild.getName(), premium.getExpiresAt().toInstant().toEpochMilli() / 1000)).queue();
} else { } else {

@ -5,6 +5,7 @@ import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
@ -21,7 +22,7 @@ import org.springframework.stereotype.Component;
public class PremiumCommand extends BatCommand { public class PremiumCommand extends BatCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BatGuild.Premium premium = guild.getPremium(); PremiumProfile premium = guild.getPremiumProfile();
EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information"); EmbedBuilder embed = EmbedUtils.genericEmbed().setAuthor("Premium Information");
if (premium.hasPremium()) { if (premium.hasPremium()) {
embed.addField("Premium", premium.hasPremium() ? "Yes" : "No", true); embed.addField("Premium", premium.hasPremium() ? "Yes" : "No", true);

@ -8,7 +8,6 @@ import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -24,11 +23,8 @@ import org.springframework.stereotype.Component;
@Component("feature:disable.sub") @Component("feature:disable.sub")
@CommandInfo(name = "disable", description = "Disables a feature") @CommandInfo(name = "disable", description = "Disables a feature")
public class DisableSubCommand extends BatSubCommand { public class DisableSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public DisableSubCommand(@NonNull GuildService guildService) { public DisableSubCommand() {
this.guildService = guildService;
super.addOption(OptionType.STRING, "feature", "The feature to disable", true); super.addOption(OptionType.STRING, "feature", "The feature to disable", true);
} }
@ -52,15 +48,14 @@ public class DisableSubCommand extends BatSubCommand {
Feature feature = FeatureService.INSTANCE.getFeature(featureName); Feature feature = FeatureService.INSTANCE.getFeature(featureName);
if (featureProfile.isFeatureDisabled(feature)) { if (featureProfile.isFeatureDisabled(feature)) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("The feature `%s` is already enabled".formatted(feature.getName())) .setDescription("The feature `%s` is already disabled".formatted(feature.getName()))
.build()).queue(); .build()).queue();
return; return;
} }
featureProfile.disableFeature(feature); featureProfile.disableFeature(feature);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully enabled the `%s` feature".formatted(feature.getName())) .setDescription("Successfully disabled the `%s` feature".formatted(feature.getName()))
.build()).queue(); .build()).queue();
} }
} }

@ -8,7 +8,6 @@ import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -24,11 +23,8 @@ import org.springframework.stereotype.Component;
@Component("feature:enable.sub") @Component("feature:enable.sub")
@CommandInfo(name = "enable", description = "Enables a feature") @CommandInfo(name = "enable", description = "Enables a feature")
public class EnableSubCommand extends BatSubCommand { public class EnableSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public EnableSubCommand(@NonNull GuildService guildService) { public EnableSubCommand() {
this.guildService = guildService;
super.addOption(OptionType.STRING, "feature", "The feature to enable", true); super.addOption(OptionType.STRING, "feature", "The feature to enable", true);
} }
@ -58,7 +54,6 @@ public class EnableSubCommand extends BatSubCommand {
} }
featureProfile.enableFeature(feature); featureProfile.enableFeature(feature);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully enabled the `%s` feature".formatted(feature.getName())) .setDescription("Successfully enabled the `%s` feature".formatted(feature.getName()))
.build()).queue(); .build()).queue();

@ -1,9 +1,12 @@
package cc.fascinated.bat.features.base.profile; package cc.fascinated.bat.features.base.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.Feature; import cc.fascinated.bat.features.Feature;
import com.google.gson.Gson;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import org.bson.Document;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -11,7 +14,8 @@ import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class FeatureProfile extends Profile { @NoArgsConstructor
public class FeatureProfile extends Serializable {
private static final FeatureState DEFAULT_STATE = FeatureState.ENABLED; private static final FeatureState DEFAULT_STATE = FeatureState.ENABLED;
/** /**
@ -19,19 +23,12 @@ public class FeatureProfile extends Profile {
*/ */
private Map<String, FeatureState> featureStates; private Map<String, FeatureState> featureStates;
public FeatureProfile() {
super("feature");
}
/** /**
* Gets the feature states * Gets the feature states
* *
* @return the feature states * @return the feature states
*/ */
public FeatureState getFeatureState(Feature feature) { public FeatureState getFeatureState(Feature feature) {
if (this.featureStates == null) {
this.featureStates = new HashMap<>();
}
if (feature == null) { if (feature == null) {
return DEFAULT_STATE; return DEFAULT_STATE;
} }
@ -85,9 +82,6 @@ public class FeatureProfile extends Profile {
* @param state the state to set * @param state the state to set
*/ */
public void setFeatureState(Feature feature, FeatureState state) { public void setFeatureState(Feature feature, FeatureState state) {
if (this.featureStates == null) {
this.featureStates = new HashMap<>();
}
this.featureStates.put(feature.getName().toUpperCase(), state); this.featureStates.put(feature.getName().toUpperCase(), state);
} }
@ -96,6 +90,23 @@ public class FeatureProfile extends Profile {
this.featureStates = null; this.featureStates = null;
} }
@Override
public void load(Document document, Gson gson) {
this.featureStates = new HashMap<>();
for (String key : document.keySet()) {
this.featureStates.put(key, FeatureState.valueOf(document.getString(key)));
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : this.featureStates.keySet()) {
document.put(key, this.featureStates.get(key).name());
}
return document;
}
@AllArgsConstructor @Getter @AllArgsConstructor @Getter
public enum FeatureState { public enum FeatureState {
ENABLED(":white_check_mark:"), ENABLED(":white_check_mark:"),
@ -104,6 +115,6 @@ public class FeatureProfile extends Profile {
/** /**
* The emoji for the feature state * The emoji for the feature state
*/ */
private String emoji; private final String emoji;
} }
} }

@ -31,9 +31,8 @@ public class BirthdayFeature extends Feature implements EventListener {
@Override @Override
public void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) { public void onGuildMemberLeave(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull GuildMemberRemoveEvent event) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
profile.removeBirthday(user.getId()); profile.removeBirthday(user.getId());
guildService.saveGuild(guild);
} }
/** /**
@ -41,8 +40,8 @@ public class BirthdayFeature extends Feature implements EventListener {
*/ */
@Scheduled(cron = "0 1 0 * * *") @Scheduled(cron = "0 1 0 * * *")
private void checkBirthdays() { private void checkBirthdays() {
for (BatGuild guild : guildService.getAllGuilds()) { for (BatGuild guild : guildService.getGuilds().values()) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
profile.checkBirthdays(guild); profile.checkBirthdays(guild);
} }
} }

@ -1,9 +1,10 @@
package cc.fascinated.bat.features.birthday; package cc.fascinated.bat.features.birthday;
import lombok.AllArgsConstructor; import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.bson.Document;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -11,11 +12,9 @@ import java.util.Date;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@AllArgsConstructor
@NoArgsConstructor
@Getter @Getter
@Setter @Setter
public class UserBirthday { public class UserBirthday extends Serializable {
/** /**
* The user's birthday * The user's birthday
*/ */
@ -43,4 +42,23 @@ public class UserBirthday {
} }
return age; return age;
} }
@Override
public void load(Document document, Gson gson) {
this.birthday = document.getDate("birthday");
this.hidden = document.getBoolean("hidden", false);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("birthday", this.birthday);
document.put("hidden", this.hidden);
return document;
}
@Override
public void reset() {
}
} }

@ -7,7 +7,6 @@ import cc.fascinated.bat.common.TextChannelUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@ -26,17 +25,14 @@ import org.springframework.stereotype.Component;
@Component("birthday:channel.sub") @Component("birthday:channel.sub")
@CommandInfo(name = "channel", description = "Sets the birthday notification channel", requiredPermissions = Permission.MANAGE_SERVER) @CommandInfo(name = "channel", description = "Sets the birthday notification channel", requiredPermissions = Permission.MANAGE_SERVER)
public class ChannelSubCommand extends BatSubCommand { public class ChannelSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public ChannelSubCommand(GuildService guildService) { public ChannelSubCommand() {
super.addOption(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false); super.addOption(OptionType.CHANNEL, "channel", "The channel birthdays will be sent in", false);
this.guildService = guildService;
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping option = interaction.getOption("channel"); OptionMapping option = interaction.getOption("channel");
if (option == null) { if (option == null) {
if (!TextChannelUtils.isValidChannel(profile.getChannelId())) { if (!TextChannelUtils.isValidChannel(profile.getChannelId())) {
@ -60,8 +56,6 @@ public class ChannelSubCommand extends BatSubCommand {
} }
profile.setChannelId(targetChannel.getId()); profile.setChannelId(targetChannel.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the birthday channel to %s".formatted(targetChannel.asTextChannel().getAsMention())) .setDescription("Successfully set the birthday channel to %s".formatted(targetChannel.asTextChannel().getAsMention()))
.build()).queue(); .build()).queue();

@ -6,7 +6,6 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
@ -24,17 +23,14 @@ import org.springframework.stereotype.Component;
@Component("birthday:message.sub") @Component("birthday:message.sub")
@CommandInfo(name = "message", description = "Changes the message that is sent when it is a user's birthday", requiredPermissions = Permission.MANAGE_SERVER) @CommandInfo(name = "message", description = "Changes the message that is sent when it is a user's birthday", requiredPermissions = Permission.MANAGE_SERVER)
public class MessageSubCommand extends BatSubCommand { public class MessageSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public MessageSubCommand(GuildService guildService) { public MessageSubCommand() {
super.addOption(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true); super.addOption(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true);
this.guildService = guildService;
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping messageOption = interaction.getOption("message"); OptionMapping messageOption = interaction.getOption("message");
if (messageOption == null) { if (messageOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() interaction.replyEmbeds(EmbedUtils.errorEmbed()
@ -58,7 +54,6 @@ public class MessageSubCommand extends BatSubCommand {
} }
profile.setMessage(message); profile.setMessage(message);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated the birthday message!\n\n**Message:** %s".formatted(profile.getBirthdayMessage(user.getDiscordUser()))) .setDescription("You have updated the birthday message!\n\n**Message:** %s".formatted(profile.getBirthdayMessage(user.getDiscordUser())))
.build()).queue(); .build()).queue();

@ -7,7 +7,6 @@ import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -24,17 +23,14 @@ import org.springframework.stereotype.Component;
@Component("birthday:private.sub") @Component("birthday:private.sub")
@CommandInfo(name = "private", description = "Changes whether your birthday is private or not") @CommandInfo(name = "private", description = "Changes whether your birthday is private or not")
public class PrivateSubCommand extends BatSubCommand { public class PrivateSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public PrivateSubCommand(@NonNull GuildService guildService) { public PrivateSubCommand() {
this.guildService = guildService;
super.addOption(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true); super.addOption(OptionType.BOOLEAN, "enabled", "Whether your birthday is private or not", true);
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
OptionMapping enabledOption = interaction.getOption("enabled"); OptionMapping enabledOption = interaction.getOption("enabled");
if (enabledOption == null) { if (enabledOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() interaction.replyEmbeds(EmbedUtils.errorEmbed()
@ -53,8 +49,6 @@ public class PrivateSubCommand extends BatSubCommand {
} }
birthday.setHidden(enabled); birthday.setHidden(enabled);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Your birthday privacy settings have been updated\n\n**Private:** " + (enabled ? "Yes" : "No")) .setDescription("Your birthday privacy settings have been updated\n\n**Private:** " + (enabled ? "Yes" : "No"))
.build()).queue(); .build()).queue();

@ -6,12 +6,10 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -21,20 +19,11 @@ import org.springframework.stereotype.Component;
@Component("birthday:remove.sub") @Component("birthday:remove.sub")
@CommandInfo(name = "remove", description = "Remove your birthday from this guild") @CommandInfo(name = "remove", description = "Remove your birthday from this guild")
public class RemoveSubCommand extends BatSubCommand { public class RemoveSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public RemoveSubCommand(GuildService guildService) {
this.guildService = guildService;
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
profile.removeBirthday(user.getId()); profile.removeBirthday(user.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Your birthday has been removed from this guild") .setDescription("Your birthday has been removed from this guild")
.build()).queue(); .build()).queue();

@ -7,7 +7,6 @@ import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile; import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -29,17 +28,15 @@ import java.util.Date;
@CommandInfo(name = "set", description = "Add your birthday to this guild") @CommandInfo(name = "set", description = "Add your birthday to this guild")
public class SetSubCommand extends BatSubCommand { public class SetSubCommand extends BatSubCommand {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy"); private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy");
private final GuildService guildService;
@Autowired @Autowired
public SetSubCommand(GuildService guildService) { public SetSubCommand() {
super.addOption(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true); super.addOption(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true);
this.guildService = guildService;
} }
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.") .setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")
@ -66,9 +63,10 @@ public class SetSubCommand extends BatSubCommand {
return; return;
} }
profile.addBirthday(member.getId(), new UserBirthday(birthday, false)); UserBirthday userBirthday = new UserBirthday();
guildService.saveGuild(guild); userBirthday.setBirthday(birthday);
userBirthday.setHidden(false);
profile.addBirthday(member.getId(), userBirthday);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated your birthday!") .setDescription("You have updated your birthday!")
.build()).queue(); .build()).queue();

@ -34,7 +34,7 @@ public class ViewSubCommand extends BatSubCommand {
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class); BirthdayProfile profile = guild.getBirthdayProfile();
if (!profile.hasChannelSetup()) { if (!profile.hasChannelSetup()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed() interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.") .setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")

@ -1,14 +1,17 @@
package cc.fascinated.bat.features.birthday.profile; package cc.fascinated.bat.features.birthday.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.birthday.UserBirthday; import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import java.util.*; import java.util.*;
@ -17,7 +20,8 @@ import java.util.*;
*/ */
@Getter @Getter
@Setter @Setter
public class BirthdayProfile extends Profile { @NoArgsConstructor
public class BirthdayProfile extends Serializable {
private static final String DEFAULT_MESSAGE = "Happy Birthday {user} :tada: :birthday: You are now {age} years old!"; private static final String DEFAULT_MESSAGE = "Happy Birthday {user} :tada: :birthday: You are now {age} years old!";
/** /**
@ -35,10 +39,6 @@ public class BirthdayProfile extends Profile {
*/ */
private String message = DEFAULT_MESSAGE; private String message = DEFAULT_MESSAGE;
public BirthdayProfile() {
super("birthday");
}
/** /**
* Adds a birthday to be tracked * Adds a birthday to be tracked
* *
@ -202,4 +202,27 @@ public class BirthdayProfile extends Profile {
birthdays.clear(); birthdays.clear();
channelId = null; channelId = null;
} }
@Override
public void load(Document document, Gson gson) {
birthdays = new HashMap<>();
for (String key : document.keySet()) {
UserBirthday userBirthday = new UserBirthday();
userBirthday.load((Document) document.get(key), gson);
birthdays.put(key, userBirthday);
}
channelId = document.getString("channelId");
message = (String) document.getOrDefault("message", DEFAULT_MESSAGE);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : birthdays.keySet()) {
document.put(key, birthdays.get(key).serialize(gson));
}
document.put("channelId", channelId);
document.put("message", message);
return document;
}
} }

@ -6,7 +6,6 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService; import cc.fascinated.bat.service.FeatureService;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent;
import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent; import net.dv8tion.jda.api.events.user.update.UserUpdateGlobalNameEvent;
@ -18,13 +17,11 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class NameHistoryListener implements EventListener { public class NameHistoryListener implements EventListener {
private final UserService userService;
private final GuildService guildService; private final GuildService guildService;
private final FeatureService featureService; private final FeatureService featureService;
@Autowired @Autowired
public NameHistoryListener(@NonNull UserService userService, @NonNull GuildService guildService, @NonNull FeatureService featureService) { public NameHistoryListener(@NonNull GuildService guildService, @NonNull FeatureService featureService) {
this.userService = userService;
this.guildService = guildService; this.guildService = guildService;
this.featureService = featureService; this.featureService = featureService;
} }
@ -33,7 +30,6 @@ public class NameHistoryListener implements EventListener {
public void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) { public void onUserUpdateGlobalName(@NonNull BatUser user, String oldName, String newName, @NonNull UserUpdateGlobalNameEvent event) {
NameHistoryProfile profile = user.getNameHistoryProfile(); NameHistoryProfile profile = user.getNameHistoryProfile();
profile.addName(newName); profile.addName(newName);
userService.saveUser(user);
} }
@Override @Override
@ -46,6 +42,5 @@ public class NameHistoryListener implements EventListener {
cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile profile = guild.getNameHistoryProfile(); cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile profile = guild.getNameHistoryProfile();
profile.addName(user, newName); profile.addName(user, newName);
guildService.saveGuild(guild);
} }
} }

@ -1,22 +1,42 @@
package cc.fascinated.bat.features.namehistory; package cc.fascinated.bat.features.namehistory;
import lombok.AllArgsConstructor; import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.bson.Document;
import java.util.Date; import java.util.Date;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@AllArgsConstructor @Getter @Getter @Setter
public class TrackedName { public class TrackedName extends Serializable {
/** /**
* The new name of the user * The new name of the user
*/ */
private final String name; private String name;
/** /**
* The date the name was changed * The date the name was changed
*/ */
private final Date changedDate; private Date changedDate;
@Override
public void load(Document document, Gson gson) {
this.name = document.getString("name");
this.changedDate = document.getDate("changedDate");
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("name", this.name);
document.put("changedDate", this.changedDate);
return document;
}
@Override
public void reset() {}
} }

@ -1,22 +1,25 @@
package cc.fascinated.bat.features.namehistory.profile.guild; package cc.fascinated.bat.features.namehistory.profile.guild;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.namehistory.NameHistoryFeature; import cc.fascinated.bat.features.namehistory.NameHistoryFeature;
import cc.fascinated.bat.features.namehistory.TrackedName; import cc.fascinated.bat.features.namehistory.TrackedName;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import com.google.gson.Gson;
import lombok.NoArgsConstructor;
import org.bson.Document;
import java.util.*; import java.util.*;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class NameHistoryProfile extends Profile { @NoArgsConstructor
public class NameHistoryProfile extends Serializable {
/**
* The name history of the user
*/
private Map<String, List<TrackedName>> nameHistory; private Map<String, List<TrackedName>> nameHistory;
public NameHistoryProfile() {
super("name-history");
}
/** /**
* Gets the name history of the user * Gets the name history of the user
* *
@ -50,7 +53,10 @@ public class NameHistoryProfile extends Profile {
* @param name the name to add * @param name the name to add
*/ */
public void addName(BatUser user, String name) { public void addName(BatUser user, String name) {
getNameHistory(user).add(new TrackedName(name, new Date())); TrackedName trackedName = new TrackedName();
trackedName.setName(name);
trackedName.setChangedDate(new Date());
getNameHistory(user).add(trackedName);
cleanup(); cleanup();
} }
@ -75,4 +81,31 @@ public class NameHistoryProfile extends Profile {
public void reset() { public void reset() {
this.nameHistory = null; this.nameHistory = null;
} }
@Override
public void load(Document document, Gson gson) {
this.nameHistory = new HashMap<>();
for (String key : document.keySet()) {
List<TrackedName> trackedNames = new LinkedList<>();
for (Document trackedNameDocument : (List<Document>) document.get(key)) {
TrackedName trackedName = new TrackedName();
trackedName.load(trackedNameDocument, gson);
trackedNames.add(trackedName);
}
this.nameHistory.put(key, trackedNames);
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
for (String key : this.nameHistory.keySet()) {
List<Document> trackedNames = new LinkedList<>();
for (TrackedName trackedName : this.nameHistory.get(key)) {
trackedNames.add(trackedName.serialize(gson));
}
document.put(key, trackedNames);
}
return document;
}
} }

@ -1,8 +1,11 @@
package cc.fascinated.bat.features.namehistory.profile.user; package cc.fascinated.bat.features.namehistory.profile.user;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.namehistory.NameHistoryFeature; import cc.fascinated.bat.features.namehistory.NameHistoryFeature;
import cc.fascinated.bat.features.namehistory.TrackedName; import cc.fascinated.bat.features.namehistory.TrackedName;
import com.google.gson.Gson;
import lombok.NoArgsConstructor;
import org.bson.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -12,13 +15,13 @@ import java.util.List;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
public class NameHistoryProfile extends Profile { @NoArgsConstructor
public class NameHistoryProfile extends Serializable {
/**
* The name history of the user
*/
private List<TrackedName> nameHistory; private List<TrackedName> nameHistory;
public NameHistoryProfile() {
super("name-history");
}
/** /**
* Gets the name history of the user * Gets the name history of the user
* *
@ -49,7 +52,10 @@ public class NameHistoryProfile extends Profile {
if (this.nameHistory == null) { if (this.nameHistory == null) {
this.nameHistory = new LinkedList<>(); this.nameHistory = new LinkedList<>();
} }
this.nameHistory.add(new TrackedName(name, new Date())); TrackedName trackedName = new TrackedName();
trackedName.setName(name);
trackedName.setChangedDate(new Date());
this.nameHistory.add(trackedName);
cleanup(); cleanup();
} }
@ -72,4 +78,29 @@ public class NameHistoryProfile extends Profile {
public void reset() { public void reset() {
this.nameHistory = null; this.nameHistory = null;
} }
@Override
public void load(Document document, Gson gson) {
this.nameHistory = new LinkedList<>();
for (Document trackedNameDocument : document.getList("nameHistory", Document.class)) {
TrackedName trackedName = new TrackedName();
trackedName.load(trackedNameDocument, gson);
this.nameHistory.add(trackedName);
}
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
List<Document> trackedNames = new ArrayList<>();
for (TrackedName trackedName : this.nameHistory) {
Document trackedNameDocument = new Document();
trackedNameDocument.put("name", trackedName.getName());
trackedNameDocument.put("changedDate", trackedName.getChangedDate());
trackedNames.add(trackedNameDocument);
}
document.put("nameHistory", trackedNames);
return document;
}
} }

@ -67,7 +67,6 @@ public class NumberOneScoreFeedListener implements EventListener {
if (channel == null) { if (channel == null) {
log.error("Scoresaber user feed channel is null for guild {}, removing the stored channel.", guild.getId()); log.error("Scoresaber user feed channel is null for guild {}, removing the stored channel.", guild.getId());
profile.setChannelId(null); profile.setChannelId(null);
guildService.saveGuild(batGuild);
continue; continue;
} }
channel.sendMessageEmbeds(ScoreSaberFeature.buildScoreEmbed(score)).queue(); channel.sendMessageEmbeds(ScoreSaberFeature.buildScoreEmbed(score)).queue();

@ -53,7 +53,6 @@ public class UserScoreFeedListener implements EventListener {
if (channel == null) { if (channel == null) {
log.error("Scoresaber user feed channel is null for guild {}, removing the stored channel.", guild.getId()); log.error("Scoresaber user feed channel is null for guild {}, removing the stored channel.", guild.getId());
profile.setChannelId(null); profile.setChannelId(null);
guildService.saveGuild(batGuild);
continue; continue;
} }
channel.sendMessageEmbeds(ScoreSaberFeature.buildScoreEmbed(score)).queue(); channel.sendMessageEmbeds(ScoreSaberFeature.buildScoreEmbed(score)).queue();

@ -7,7 +7,6 @@ import cc.fascinated.bat.common.TextChannelUtils;
import cc.fascinated.bat.features.scoresaber.profile.guild.NumberOneScoreFeedProfile; import cc.fascinated.bat.features.scoresaber.profile.guild.NumberOneScoreFeedProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.ChannelType;
@ -25,12 +24,9 @@ import org.springframework.stereotype.Component;
@Component("scoresaber-number-one-feed:channel.sub") @Component("scoresaber-number-one-feed:channel.sub")
@CommandInfo(name = "channel", description = "Sets the feed channel") @CommandInfo(name = "channel", description = "Sets the feed channel")
public class ChannelSubCommand extends BatSubCommand { public class ChannelSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public ChannelSubCommand(GuildService guildService) { public ChannelSubCommand() {
super.addOption(OptionType.CHANNEL, "channel", "The channel scores are sent in", false); super.addOption(OptionType.CHANNEL, "channel", "The channel scores are sent in", false);
this.guildService = guildService;
} }
@Override @Override
@ -59,8 +55,6 @@ public class ChannelSubCommand extends BatSubCommand {
} }
profile.setChannelId(targetChannel.getId()); profile.setChannelId(targetChannel.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the feed channel to %s".formatted(targetChannel.asTextChannel().getAsMention())) .setDescription("Successfully set the feed channel to %s".formatted(targetChannel.asTextChannel().getAsMention()))
.build()).queue(); .build()).queue();

@ -6,12 +6,10 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.scoresaber.profile.guild.NumberOneScoreFeedProfile; import cc.fascinated.bat.features.scoresaber.profile.guild.NumberOneScoreFeedProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -20,18 +18,10 @@ import org.springframework.stereotype.Component;
@Component("scoresaber-number-one-feed:reset.sub") @Component("scoresaber-number-one-feed:reset.sub")
@CommandInfo(name = "reset", description = "Resets the settings") @CommandInfo(name = "reset", description = "Resets the settings")
public class ResetSubCommand extends BatSubCommand { public class ResetSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public ResetSubCommand(GuildService guildService) {
this.guildService = guildService;
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
NumberOneScoreFeedProfile profile = guild.getProfile(NumberOneScoreFeedProfile.class); NumberOneScoreFeedProfile profile = guild.getProfile(NumberOneScoreFeedProfile.class);
profile.reset(); profile.reset();
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully reset the settings.") .setDescription("Successfully reset the settings.")

@ -7,7 +7,6 @@ import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberAccountToken; import cc.fascinated.bat.model.token.beatsaber.scoresaber.ScoreSaberAccountToken;
import cc.fascinated.bat.service.ScoreSaberService; import cc.fascinated.bat.service.ScoreSaberService;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -24,13 +23,11 @@ import org.springframework.stereotype.Component;
@CommandInfo(name = "link", description = "Links your ScoreSaber profile") @CommandInfo(name = "link", description = "Links your ScoreSaber profile")
public class LinkSubCommand extends BatSubCommand { public class LinkSubCommand extends BatSubCommand {
private final ScoreSaberService scoreSaberService; private final ScoreSaberService scoreSaberService;
private final UserService userService;
@Autowired @Autowired
public LinkSubCommand(@NonNull ScoreSaberService scoreSaberService, @NonNull UserService userService) { public LinkSubCommand(@NonNull ScoreSaberService scoreSaberService) {
super.addOption(OptionType.STRING, "link", "Link your ScoreSaber profile", true); super.addOption(OptionType.STRING, "link", "Link your ScoreSaber profile", true);
this.scoreSaberService = scoreSaberService; this.scoreSaberService = scoreSaberService;
this.userService = userService;
} }
@Override @Override
@ -65,7 +62,6 @@ public class LinkSubCommand extends BatSubCommand {
} }
user.getScoreSaberProfile().setAccountId(id); user.getScoreSaberProfile().setAccountId(id);
userService.saveUser(user);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully linked your [ScoreSaber](%s) profile".formatted("https://scoresaber.com/u/%s".formatted(id))) .setDescription("Successfully linked your [ScoreSaber](%s) profile".formatted("https://scoresaber.com/u/%s".formatted(id)))
.build()).queue(); .build()).queue();

@ -6,12 +6,10 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.scoresaber.profile.user.ScoreSaberProfile; import cc.fascinated.bat.features.scoresaber.profile.user.ScoreSaberProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -20,18 +18,10 @@ import org.springframework.stereotype.Component;
@Component("scoresaber:reset.sub") @Component("scoresaber:reset.sub")
@CommandInfo(name = "reset", description = "Reset your settings") @CommandInfo(name = "reset", description = "Reset your settings")
public class ResetSubCommand extends BatSubCommand { public class ResetSubCommand extends BatSubCommand {
private final UserService userService;
@Autowired
public ResetSubCommand(@NonNull UserService userService) {
this.userService = userService;
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
ScoreSaberProfile profile = user.getScoreSaberProfile(); ScoreSaberProfile profile = user.getScoreSaberProfile();
profile.reset(); profile.reset();
userService.saveUser(user);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully reset your settings.") .setDescription("Successfully reset your settings.")

@ -7,7 +7,6 @@ import cc.fascinated.bat.common.TextChannelUtils;
import cc.fascinated.bat.features.scoresaber.profile.guild.UserScoreFeedProfile; import cc.fascinated.bat.features.scoresaber.profile.guild.UserScoreFeedProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.ChannelType;
@ -25,12 +24,9 @@ import org.springframework.stereotype.Component;
@Component("scoresaber-user-feed:channel.sub") @Component("scoresaber-user-feed:channel.sub")
@CommandInfo(name = "channel", description = "Sets the feed channel") @CommandInfo(name = "channel", description = "Sets the feed channel")
public class ChannelSubCommand extends BatSubCommand { public class ChannelSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired @Autowired
public ChannelSubCommand(GuildService guildService) { public ChannelSubCommand() {
super.addOption(OptionType.CHANNEL, "channel", "The channel scores are sent in", false); super.addOption(OptionType.CHANNEL, "channel", "The channel scores are sent in", false);
this.guildService = guildService;
} }
@Override @Override
@ -59,8 +55,6 @@ public class ChannelSubCommand extends BatSubCommand {
} }
profile.setChannelId(targetChannel.getId()); profile.setChannelId(targetChannel.getId());
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully set the feed channel to %s".formatted(targetChannel.asTextChannel().getAsMention())) .setDescription("Successfully set the feed channel to %s".formatted(targetChannel.asTextChannel().getAsMention()))
.build()).queue(); .build()).queue();

@ -6,12 +6,10 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.scoresaber.profile.guild.UserScoreFeedProfile; import cc.fascinated.bat.features.scoresaber.profile.guild.UserScoreFeedProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -20,19 +18,10 @@ import org.springframework.stereotype.Component;
@Component("scoresaber-user-feed:reset.sub") @Component("scoresaber-user-feed:reset.sub")
@CommandInfo(name = "reset", description = "Resets the settings") @CommandInfo(name = "reset", description = "Resets the settings")
public class ResetSubCommand extends BatSubCommand { public class ResetSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public ResetSubCommand(GuildService guildService) {
this.guildService = guildService;
}
@Override @Override
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
UserScoreFeedProfile profile = guild.getProfile(UserScoreFeedProfile.class); UserScoreFeedProfile profile = guild.getProfile(UserScoreFeedProfile.class);
profile.reset(); profile.reset();
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully reset the settings.") .setDescription("Successfully reset the settings.")
.build()).queue(); .build()).queue();

@ -83,6 +83,5 @@ public class UserSubCommand extends BatSubCommand {
target.getAsMention() target.getAsMention()
)) ))
.build()).queue(); .build()).queue();
guildService.saveGuild(guild);
} }
} }

@ -1,26 +1,26 @@
package cc.fascinated.bat.features.scoresaber.profile.guild; package cc.fascinated.bat.features.scoresaber.profile.guild;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Getter
@Setter @Setter
public class NumberOneScoreFeedProfile extends Profile { @NoArgsConstructor
public class NumberOneScoreFeedProfile extends Serializable {
/** /**
* The channel ID of the score feed * The channel ID of the score feed
*/ */
private String channelId; private String channelId;
public NumberOneScoreFeedProfile() {
super("scoresaber-number-one-score-feed");
}
/** /**
* Gets the channel as a TextChannel * Gets the channel as a TextChannel
* *
@ -34,4 +34,16 @@ public class NumberOneScoreFeedProfile extends Profile {
public void reset() { public void reset() {
this.channelId = null; this.channelId = null;
} }
@Override
public void load(Document document, Gson gson) {
this.channelId = document.getString("channelId");
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("channelId", this.channelId);
return document;
}
} }

@ -1,10 +1,13 @@
package cc.fascinated.bat.features.scoresaber.profile.guild; package cc.fascinated.bat.features.scoresaber.profile.guild;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.bson.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -14,7 +17,8 @@ import java.util.List;
*/ */
@Getter @Getter
@Setter @Setter
public class UserScoreFeedProfile extends Profile { @NoArgsConstructor
public class UserScoreFeedProfile extends Serializable {
/** /**
* The channel ID of the score feed * The channel ID of the score feed
*/ */
@ -25,10 +29,6 @@ public class UserScoreFeedProfile extends Profile {
*/ */
private List<String> trackedUsers; private List<String> trackedUsers;
public UserScoreFeedProfile() {
super("scoresaber-user-score-feed");
}
/** /**
* Gets the tracked users * Gets the tracked users
* *
@ -92,4 +92,18 @@ public class UserScoreFeedProfile extends Profile {
this.channelId = null; this.channelId = null;
this.trackedUsers = null; this.trackedUsers = null;
} }
@Override
public void load(Document document, Gson gson) {
this.channelId = document.getString("channelId");
this.trackedUsers = document.getList("trackedUsers", String.class, new ArrayList<>());
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("channelId", this.channelId);
document.put("trackedUsers", this.trackedUsers);
return document;
}
} }

@ -1,26 +1,38 @@
package cc.fascinated.bat.features.scoresaber.profile.user; package cc.fascinated.bat.features.scoresaber.profile.user;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.bson.Document;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Setter @Setter
@Getter @Getter
public class ScoreSaberProfile extends Profile { @NoArgsConstructor
public class ScoreSaberProfile extends Serializable {
/** /**
* The Account ID of the ScoreSaber profile * The Account ID of the ScoreSaber profile
*/ */
private String accountId; private String accountId;
public ScoreSaberProfile() {
super("scoresaber");
}
@Override @Override
public void reset() { public void reset() {
this.accountId = null; this.accountId = null;
} }
@Override
public void load(Document document, Gson gson) {
this.accountId = document.getString("accountId");
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("accountId", this.accountId);
return document;
}
} }

@ -9,13 +9,11 @@ import cc.fascinated.bat.exception.BatException;
import cc.fascinated.bat.features.spotify.profile.SpotifyProfile; import cc.fascinated.bat.features.spotify.profile.SpotifyProfile;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.UserService;
import lombok.NonNull; import lombok.NonNull;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction; import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
@ -24,13 +22,6 @@ import org.springframework.stereotype.Component;
@Component @Component
@CommandInfo(name = "unlink", description = "Unlink your Spotify account") @CommandInfo(name = "unlink", description = "Unlink your Spotify account")
public class UnlinkSubCommand extends BatSubCommand implements EventListener { public class UnlinkSubCommand extends BatSubCommand implements EventListener {
private final UserService userService;
@Autowired
public UnlinkSubCommand(@NonNull UserService userService) {
this.userService = userService;
}
@Override @SneakyThrows @Override @SneakyThrows
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) { public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction interaction) {
SpotifyProfile profile = user.getProfile(SpotifyProfile.class); SpotifyProfile profile = user.getProfile(SpotifyProfile.class);
@ -39,7 +30,6 @@ public class UnlinkSubCommand extends BatSubCommand implements EventListener {
} }
profile.reset(); profile.reset();
userService.saveUser(user);
interaction.replyEmbeds(EmbedUtils.successEmbed() interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("%s Successfully unlinked your Spotify account.".formatted(Emojis.CHECK_MARK_EMOJI)) .setDescription("%s Successfully unlinked your Spotify account.".formatted(Emojis.CHECK_MARK_EMOJI))
.build()) .build())

@ -1,14 +1,16 @@
package cc.fascinated.bat.features.spotify.profile; package cc.fascinated.bat.features.spotify.profile;
import cc.fascinated.bat.common.Profile; import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.bson.Document;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Setter @Getter @Setter
public class SpotifyProfile extends Profile { public class SpotifyProfile extends Serializable {
/** /**
* The access token * The access token
*/ */
@ -24,10 +26,6 @@ public class SpotifyProfile extends Profile {
*/ */
private Long expiresAt; private Long expiresAt;
public SpotifyProfile() {
super("spotify");
}
/** /**
* Checks if the account has a linked account * Checks if the account has a linked account
* *
@ -42,4 +40,20 @@ public class SpotifyProfile extends Profile {
this.accessToken = null; this.accessToken = null;
this.refreshToken = null; this.refreshToken = null;
} }
@Override
public void load(Document document, Gson gson) {
this.accessToken = document.getString("accessToken");
this.refreshToken = document.getString("refreshToken");
this.expiresAt = document.getLong("expiresAt");
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("accessToken", this.accessToken);
document.put("refreshToken", this.refreshToken);
document.put("expiresAt", this.expiresAt);
return document;
}
} }

@ -1,44 +0,0 @@
package cc.fascinated.bat.migrations.changelogs;
import com.mongodb.client.FindIterable;
import io.mongock.api.annotations.ChangeUnit;
import io.mongock.api.annotations.Execution;
import io.mongock.api.annotations.RollbackExecution;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
/**
* @author Fascinated (fascinated7)
*/
@ChangeUnit(id="birthday-changelog", order = "001", author = "fascinated7")
public class BirthdayProfileChangelog {
private final MongoTemplate mongoTemplate;
public BirthdayProfileChangelog(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Execution
public void changeSet() {
FindIterable<Document> guilds = mongoTemplate.getCollection("guilds").find();
guilds.forEach(guild -> {
Document profiles = guild.get("profiles", Document.class);
if (profiles == null) {
return;
}
Document birthdayProfile = profiles.get("birthday", Document.class);
if (birthdayProfile == null) {
return;
}
birthdayProfile.remove("birthdays");
profiles.put("birthday", birthdayProfile);
guild.put("profiles", profiles);
mongoTemplate.getCollection("guilds").replaceOne(new Document("_id", guild.get("_id")), guild);
});
}
@RollbackExecution
public void rollback() {
// DO NOTHING
}
}

@ -1,86 +0,0 @@
package cc.fascinated.bat.migrations.changelogs;
import com.mongodb.client.FindIterable;
import io.mongock.api.annotations.ChangeUnit;
import io.mongock.api.annotations.Execution;
import io.mongock.api.annotations.RollbackExecution;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
/**
* @author Fascinated (fascinated7)
*/
@ChangeUnit(id = "scoresaber-profile-rename-package-changelog", order = "001", author = "fascinated7")
public class ScoresaberProfileRenamePackageChangelog {
private final MongoTemplate mongoTemplate;
public ScoresaberProfileRenamePackageChangelog(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Execution
public void changeSet() {
FindIterable<Document> guilds = mongoTemplate.getCollection("guilds").find();
guilds.forEach(guild -> {
Document profiles = guild.get("profiles", Document.class);
if (profiles == null) {
return;
}
// NumberOneScoreFeedProfile
Document numberOneScoreFeedProfile = profiles.get("scoresaber-number-one-score-feed", Document.class);
if (numberOneScoreFeedProfile == null) {
return;
}
numberOneScoreFeedProfile.put("_class", "cc.fascinated.bat.features.scoresaber.profile.guild.NumberOneScoreFeedProfile");
profiles.put("scoresaber-number-one-score-feed", numberOneScoreFeedProfile);
guild.put("profiles", profiles);
mongoTemplate.getCollection("guilds").replaceOne(new Document("_id", guild.get("_id")), guild);
});
guilds.forEach(guild -> {
Document profiles = guild.get("profiles", Document.class);
if (profiles == null) {
return;
}
// UserScoreFeedProfile
Document userScoreFeedProfile = profiles.get("scoresaber-number-one-score-feed", Document.class);
if (userScoreFeedProfile == null) {
return;
}
userScoreFeedProfile.put("_class", "cc.fascinated.bat.features.scoresaber.profile.guild.UserScoreFeedProfile");
profiles.put("scoresaber-user-score-feed", userScoreFeedProfile);
guild.put("profiles", profiles);
mongoTemplate.getCollection("guilds").replaceOne(new Document("_id", guild.get("_id")), guild);
});
FindIterable<Document> users = mongoTemplate.getCollection("users").find();
users.forEach(guild -> {
Document profiles = guild.get("profiles", Document.class);
if (profiles == null) {
return;
}
// ScoreSaberProfile
Document userScoreFeedProfile = profiles.get("scoresaber", Document.class);
if (userScoreFeedProfile == null) {
return;
}
userScoreFeedProfile.put("_class", "cc.fascinated.bat.features.scoresaber.profile.user.ScoreSaberProfile");
profiles.put("scoresaber", userScoreFeedProfile);
guild.put("profiles", profiles);
mongoTemplate.getCollection("users").replaceOne(new Document("_id", guild.get("_id")), guild);
});
}
@RollbackExecution
public void rollback() {
// DO NOTHING
}
}

@ -1,25 +1,41 @@
package cc.fascinated.bat.model; package cc.fascinated.bat.model;
import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.common.ProfileHolder; import cc.fascinated.bat.common.ProfileHolder;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.base.profile.FeatureProfile; import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile; import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile;
import cc.fascinated.bat.premium.PremiumProfile;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import lombok.*; import cc.fascinated.bat.service.MongoService;
import com.mongodb.client.model.ReplaceOptions;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@RequiredArgsConstructor
@Getter @Getter
@Setter @Setter
@Document(collection = "guilds") @Document(collection = "guilds")
public class BatGuild extends ProfileHolder { public class BatGuild extends ProfileHolder {
private static final Logger log = LoggerFactory.getLogger(BatGuild.class);
/**
* The document that belongs to this guild
*/
private final org.bson.Document document;
/** /**
* The ID of the guild * The ID of the guild
*/ */
@ -30,23 +46,13 @@ public class BatGuild extends ProfileHolder {
/** /**
* The time this guild was joined * The time this guild was joined
*/ */
private Date createdAt = new Date(); private Date createdAt;
/** public BatGuild(@NonNull String id, @NonNull org.bson.Document document) {
* The premium information for the guild this.id = id;
*/ this.document = document;
private Premium premium; boolean newAccount = this.document.isEmpty();
this.createdAt = newAccount ? new Date() : document.getDate("createdAt");
/**
* The premium information for the guild
*
* @return the premium information
*/
public Premium getPremium() {
if (this.premium == null) {
this.premium = new Premium(null, null, null);
}
return this.premium;
} }
/** /**
@ -85,91 +91,46 @@ public class BatGuild extends ProfileHolder {
return getProfile(FeatureProfile.class); return getProfile(FeatureProfile.class);
} }
@AllArgsConstructor /**
@Getter * Gets the premium profile
@Setter *
public static class Premium { * @return the premium profile
/** */
* The time the premium was activated public PremiumProfile getPremiumProfile() {
*/ return getProfile(PremiumProfile.class);
private Date activatedAt; }
/** /**
* The time the premium expires * Gets the birthday profile
*/ *
private Date expiresAt; * @return the birthday profile
*/
public BirthdayProfile getBirthdayProfile() {
return getProfile(BirthdayProfile.class);
}
/** /**
* The type of premium * Saves the user
*/ */
private Type type; public void save() {
document.put("_id", id);
document.put("createdAt", createdAt);
/** Map<String, org.bson.Document> profileDocuments = new HashMap<>();
* Checks if the guild has premium for (Serializable profile : getProfiles().values()) {
* profileDocuments.put(profile.getClass().getSimpleName(), profile.serialize(BatApplication.GSON));
* @return whether the guild has premium
*/
public boolean hasPremium() {
return this.type == Type.INFINITE || (this.expiresAt != null && this.expiresAt.after(new Date()));
} }
document.put("profiles", profileDocuments);
/** MongoService.INSTANCE.getGuildsCollection().replaceOne(
* Adds a month to the premium time new org.bson.Document("_id", id),
*/ this.getDocument(),
public void addTime(int months) { new ReplaceOptions().upsert(true)
if (this.type == null) { // If the type is null, set it to monthly );
this.type = Type.MONTHLY; }
}
if (this.expiresAt == null) {
this.expiresAt = new Date();
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MONTH, months);
this.expiresAt = calendar.getTime();
this.type = Type.MONTHLY;
}
/** @Override
* Adds a month to the premium time public <T extends Serializable> T getProfile(Class<T> clazz) {
*/ return getProfileFromDocument(clazz, document);
public void addTime() {
addTime(1);
}
/**
* Adds infinite time to the premium
*/
public void addInfiniteTime() {
this.type = Type.INFINITE;
this.expiresAt = null;
this.activatedAt = new Date();
}
/**
* Removes the premium from the guild
*/
public void removePremium() {
this.activatedAt = null;
this.expiresAt = null;
this.type = null;
}
/**
* Checks if the premium is infinite
*
* @return whether the premium is infinite
*/
public boolean isInfinite() {
return this.type == Type.INFINITE;
}
/**
* The premium type for the guild
*/
public enum Type {
INFINITE,
MONTHLY
}
} }
} }

@ -1,18 +1,26 @@
package cc.fascinated.bat.model; package cc.fascinated.bat.model;
import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.common.ProfileHolder; import cc.fascinated.bat.common.ProfileHolder;
import cc.fascinated.bat.common.Serializable;
import cc.fascinated.bat.features.namehistory.profile.user.NameHistoryProfile; import cc.fascinated.bat.features.namehistory.profile.user.NameHistoryProfile;
import cc.fascinated.bat.features.scoresaber.profile.user.ScoreSaberProfile; import cc.fascinated.bat.features.scoresaber.profile.user.ScoreSaberProfile;
import cc.fascinated.bat.service.DiscordService; import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.MongoService;
import com.mongodb.client.model.ReplaceOptions;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
@ -22,6 +30,12 @@ import java.util.Date;
@Setter @Setter
@Document(collection = "users") @Document(collection = "users")
public class BatUser extends ProfileHolder { public class BatUser extends ProfileHolder {
private static final Logger log = LoggerFactory.getLogger(BatUser.class);
/**
* The document that belongs to this user
*/
private final org.bson.Document document;
/** /**
* The ID of the user * The ID of the user
*/ */
@ -32,7 +46,14 @@ public class BatUser extends ProfileHolder {
/** /**
* The time this user was created * The time this user was created
*/ */
private Date createdAt = new Date(); private Date createdAt;
public BatUser(@NonNull String id, @NonNull org.bson.Document document) {
this.id = id;
this.document = document;
boolean newAccount = this.document.isEmpty();
this.createdAt = newAccount ? new Date() : document.getDate("createdAt");
}
/** /**
* The name of the user * The name of the user
@ -67,4 +88,29 @@ public class BatUser extends ProfileHolder {
public NameHistoryProfile getNameHistoryProfile() { public NameHistoryProfile getNameHistoryProfile() {
return getProfile(NameHistoryProfile.class); return getProfile(NameHistoryProfile.class);
} }
/**
* Saves the user
*/
public void save() {
document.put("_id", id);
document.put("createdAt", createdAt);
Map<String, org.bson.Document> profileDocuments = new HashMap<>();
for (Serializable profile : getProfiles().values()) {
profileDocuments.put(profile.getClass().getSimpleName(), profile.serialize(BatApplication.GSON));
}
document.put("profiles", profileDocuments);
MongoService.INSTANCE.getUsersCollection().replaceOne(
new org.bson.Document("_id", id),
this.getDocument(),
new ReplaceOptions().upsert(true)
);
}
@Override
public <T extends Serializable> T getProfile(Class<T> clazz) {
return getProfileFromDocument(clazz, document);
}
} }

@ -0,0 +1,134 @@
package cc.fascinated.bat.premium;
import cc.fascinated.bat.common.Serializable;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bson.Document;
import java.util.Calendar;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
@NoArgsConstructor
public class PremiumProfile extends Serializable {
/**
* The time the premium was activated
*/
private Date activatedAt;
/**
* The time the premium expires
*/
private Date expiresAt;
/**
* The type of premium
*/
private Type type;
/**
* Checks if the guild has premium
*
* @return whether the guild has premium
*/
public boolean hasPremium() {
return this.type == Type.INFINITE || (this.expiresAt != null && this.expiresAt.after(new Date()));
}
/**
* Adds a month to the premium time
*/
public void addTime(int months) {
if (this.type == null) { // If the type is null, set it to monthly
this.type = Type.MONTHLY;
}
if (this.expiresAt == null) {
this.expiresAt = new Date();
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.MONTH, months);
this.expiresAt = calendar.getTime();
this.type = Type.MONTHLY;
}
/**
* Adds a month to the premium time
*/
public void addTime() {
addTime(1);
}
/**
* Adds infinite time to the premium
*/
public void addInfiniteTime() {
this.type = Type.INFINITE;
this.expiresAt = null;
this.activatedAt = new Date();
}
/**
* Removes the premium from the guild
*/
public void removePremium() {
this.activatedAt = null;
this.expiresAt = null;
this.type = null;
}
/**
* Checks if the premium is infinite
*
* @return whether the premium is infinite
*/
public boolean isInfinite() {
return this.type == Type.INFINITE;
}
/**
* Checks if the premium has expired
*
* @return whether the premium has expired
*/
public boolean hasExpired() {
return this.expiresAt != null && this.expiresAt.before(new Date());
}
/**
* The premium type for the guild
*/
public enum Type {
INFINITE,
MONTHLY
}
@Override
public void load(Document document, Gson gson) {
this.activatedAt = (Date) document.getOrDefault("activatedAt", new Date());
this.expiresAt = (Date) document.getOrDefault("expiresAt", null);
this.type = document.containsKey("type") ? Type.valueOf(document.getString("type")) : null;
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("activatedAt", this.activatedAt);
document.put("expiresAt", this.expiresAt);
document.put("type", this.type.name());
return document;
}
@Override
public void reset() {
this.activatedAt = null;
this.expiresAt = null;
this.type = null;
}
}

@ -1,10 +0,0 @@
package cc.fascinated.bat.repository;
import cc.fascinated.bat.model.BatGuild;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Fascinated (fascinated7)
*/
public interface GuildRepository extends MongoRepository<BatGuild, String> {
}

@ -1,10 +0,0 @@
package cc.fascinated.bat.repository;
import cc.fascinated.bat.model.BatUser;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
* @author Fascinated (fascinated7)
*/
public interface UserRepository extends MongoRepository<BatUser, String> {
}

@ -27,7 +27,7 @@ import java.util.*;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Service @Service
@Log4j2 @Log4j2(topic = "Command Service")
@Getter @Getter
@DependsOn("discordService") @DependsOn("discordService")
public class CommandService extends ListenerAdapter { public class CommandService extends ListenerAdapter {

@ -3,6 +3,7 @@ package cc.fascinated.bat.service;
import cc.fascinated.bat.common.NumberFormatter; import cc.fascinated.bat.common.NumberFormatter;
import cc.fascinated.bat.common.TimerUtils; import cc.fascinated.bat.common.TimerUtils;
import lombok.Getter; import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Activity;
@ -20,6 +21,7 @@ import java.util.List;
*/ */
@Service @Service
@Getter @Getter
@Log4j2(topic = "Discord Service")
public class DiscordService { public class DiscordService {
/** /**
* The JDA instance * The JDA instance
@ -37,6 +39,7 @@ public class DiscordService {
public DiscordService( public DiscordService(
@Value("${discord.token}") String token @Value("${discord.token}") String token
) throws Exception { ) throws Exception {
log.info("Starting Discord bot...");
JDA = JDABuilder.create(token, EnumSet.of( JDA = JDABuilder.create(token, EnumSet.of(
GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT, GatewayIntent.MESSAGE_CONTENT,
@ -51,6 +54,7 @@ public class DiscordService {
CacheFlag.SCHEDULED_EVENTS CacheFlag.SCHEDULED_EVENTS
).build() ).build()
.awaitReady(); .awaitReady();
log.info("Connected to Discord as {}", JDA.getSelfUser().getEffectiveName());
TimerUtils.scheduleRepeating(this::updateActivity, 0, 1000 * 60 * 2); TimerUtils.scheduleRepeating(this::updateActivity, 0, 1000 * 60 * 2);
} }

@ -27,7 +27,7 @@ import java.util.Set;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Service @Service
@Log4j2 @Log4j2(topic = "Event Service")
@DependsOn("discordService") @DependsOn("discordService")
public class EventService extends ListenerAdapter { public class EventService extends ListenerAdapter {
/** /**

@ -21,7 +21,7 @@ import java.util.Map;
*/ */
@Service @Service
@Getter @Getter
@Log4j2 @Log4j2(topic = "Feature Service")
@DependsOn("commandService") @DependsOn("commandService")
public class FeatureService { public class FeatureService {
public static FeatureService INSTANCE; public static FeatureService INSTANCE;

@ -1,57 +1,63 @@
package cc.fascinated.bat.service; package cc.fascinated.bat.service;
import cc.fascinated.bat.common.TimerUtils;
import cc.fascinated.bat.model.BatGuild; import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.repository.GuildRepository; import cc.fascinated.bat.premium.PremiumProfile;
import com.mongodb.client.model.Filters;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.events.guild.GuildJoinEvent; import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.jodah.expiringmap.ExpiringMap; import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Service @Service
@Log4j2 @Log4j2(topic = "Guild Service")
@Getter @Getter
@DependsOn("discordService") @DependsOn({"discordService", "mongoService"})
public class GuildService extends ListenerAdapter { public class GuildService extends ListenerAdapter {
private static final long SAVE_INTERVAL = TimeUnit.MINUTES.toMillis(5);
/** /**
* The cached guilds * The cached guilds
*/ */
private final Map<String, BatGuild> guilds = ExpiringMap.builder() private final Map<String, BatGuild> guilds = new HashMap<>();
.expiration(6, TimeUnit.HOURS)
.build();
/**
* The guild repository to use
*/
private final GuildRepository guildRepository;
@Autowired @Autowired
public GuildService(@NonNull GuildRepository guildRepository) { public GuildService() {
this.guildRepository = guildRepository; TimerUtils.scheduleRepeating(() -> {
long before = System.currentTimeMillis();
for (BatGuild guild : guilds.values()) {
guild.save();
}
log.info("Saved {} guilds in {}ms", guilds.size(), System.currentTimeMillis() - before);
}, SAVE_INTERVAL, SAVE_INTERVAL);
DiscordService.JDA.addEventListener(this); DiscordService.JDA.addEventListener(this);
} }
@Scheduled(cron = "0 0 0 * * *") @Scheduled(cron = "0 0 0 * * *")
private void validatePremiumStatus() { private void validatePremiumStatus() {
for (BatGuild guild : guilds.values()) { for (BatGuild guild : guilds.values()) {
BatGuild.Premium premium = guild.getPremium(); PremiumProfile premium = guild.getPremiumProfile();
if (premium.getExpiresAt() != null && premium.getExpiresAt().before(new Date())) { if (!premium.hasExpired()) {
premium.removePremium(); return;
guildRepository.save(guild);
log.info("Removed premium status from guild \"{}\"", guild.getName());
} }
premium.removePremium();
log.info("Removed premium status from guild \"{}\"", guild.getName());
} }
} }
@ -62,51 +68,37 @@ public class GuildService extends ListenerAdapter {
* @return The guild * @return The guild
*/ */
public BatGuild getGuild(@NonNull String id) { public BatGuild getGuild(@NonNull String id) {
long before = System.currentTimeMillis();
// Guild is cached
if (guilds.containsKey(id)) { if (guilds.containsKey(id)) {
return guilds.get(id); return guilds.get(id);
} }
if (DiscordService.JDA.getGuildById(id) == null) { // Guild is not cached
return null; Document document = MongoService.INSTANCE.getGuildsCollection().find(Filters.eq("_id", id)).first();
} if (document != null) {
long start = System.currentTimeMillis(); BatGuild guild = new BatGuild(id, document);
Optional<BatGuild> optionalGuild = guildRepository.findById(id);
if (optionalGuild.isPresent()) {
BatGuild guild = optionalGuild.get();
guilds.put(id, guild); guilds.put(id, guild);
log.info("Loaded guild \"{}\" in {}ms", guild.getName(),System.currentTimeMillis() - before);
return guild; return guild;
} }
BatGuild guild = guildRepository.save(new BatGuild(id)); // New guild
log.info("Created guild \"{}\" in {}ms", guild.getName(), System.currentTimeMillis() - start); BatGuild guild = new BatGuild(id, new Document());
guilds.put(id, guild);
log.info("Created guild \"{}\" - \"{}\"", guild.getName(), guild.getId());
return guild; return guild;
} }
/**
* Saves a guild
*
* @param guild The guild to save
*/
public void saveGuild(@NonNull BatGuild guild) {
guildRepository.save(guild);
}
/**
* Gets all guilds
*
* @return all guilds
*/
public List<BatGuild> getAllGuilds() {
List<BatGuild> guilds = new ArrayList<>();
for (Guild guild : DiscordService.JDA.getGuilds()) {
guilds.add(getGuild(guild.getId()));
}
return guilds;
}
@Override @Override
public final void onGuildJoin(GuildJoinEvent event) { public final void onGuildJoin(GuildJoinEvent event) {
Guild guild = event.getGuild(); BatGuild guild = getGuild(event.getGuild().getId());
log.info("Joined guild \"{}\"", guild.getName()); log.info("Joined guild \"{}\"", guild.getName());
getGuild(guild.getId()); // Ensure the guild is in the database }
@Override
public void onGuildLeave(@NotNull GuildLeaveEvent event) {
BatGuild guild = getGuild(event.getGuild().getId());
log.info("Left guild \"{}\"", guild.getName());
guild.save();
guilds.remove(guild.getId());
} }
} }

@ -0,0 +1,40 @@
package cc.fascinated.bat.service;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
/**
* @author Fascinated (fascinated7)
*/
@Service
public class MongoService {
public static MongoService INSTANCE;
private final MongoTemplate mongo;
@Autowired
public MongoService(MongoTemplate mongo) {
INSTANCE = this;
this.mongo = mongo;
}
/**
* Get the guilds collection
*
* @return The guilds collection
*/
public MongoCollection<Document> getGuildsCollection() {
return mongo.getCollection("guilds");
}
/**
* Get the users collection
*
* @return The users collection
*/
public MongoCollection<Document> getUsersCollection() {
return mongo.getCollection("users");
}
}

@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
*/ */
@Service @Service
@Getter @Getter
@Log4j2 @Log4j2(topic = "Spotify Service")
public class SpotifyService { public class SpotifyService {
/** /**
* The access token map. * The access token map.
@ -189,7 +189,6 @@ public class SpotifyService {
AuthorizationCodeCredentials credentials = api.authorizationCodeRefresh().build().execute(); AuthorizationCodeCredentials credentials = api.authorizationCodeRefresh().build().execute();
profile.setAccessToken(credentials.getAccessToken()); profile.setAccessToken(credentials.getAccessToken());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000)); profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
log.info("Refreshed Spotify token for user {}", user.getName()); log.info("Refreshed Spotify token for user {}", user.getName());
} catch (SpotifyWebApiException ex) { } catch (SpotifyWebApiException ex) {
log.error("Failed to refresh Spotify token", ex); log.error("Failed to refresh Spotify token", ex);
@ -213,7 +212,6 @@ public class SpotifyService {
profile.setAccessToken(credentials.getAccessToken()); profile.setAccessToken(credentials.getAccessToken());
profile.setRefreshToken(credentials.getRefreshToken()); profile.setRefreshToken(credentials.getRefreshToken());
profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000)); profile.setExpiresAt(System.currentTimeMillis() + (credentials.getExpiresIn() * 1000));
userService.saveUser(user);
log.info("Linked Spotify account for user {}", user.getName()); log.info("Linked Spotify account for user {}", user.getName());
} }

@ -1,73 +1,70 @@
package cc.fascinated.bat.service; package cc.fascinated.bat.service;
import cc.fascinated.bat.common.TimerUtils;
import cc.fascinated.bat.model.BatUser; import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.repository.UserRepository; import com.mongodb.client.model.Filters;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpiringMap; import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Service @Service
@Log4j2 @Log4j2(topic = "User Service")
@Getter @Getter
@DependsOn("discordService") @DependsOn({"discordService", "mongoService"})
public class UserService { public class UserService {
private static final long SAVE_INTERVAL = TimeUnit.MINUTES.toMillis(5);
/** /**
* The cached users * The cached users
*/ */
private final Map<String, BatUser> users = ExpiringMap.builder() private final Map<String, BatUser> users = new HashMap<>();
.expiration(6, TimeUnit.HOURS)
.build();
/**
* The user repository to use
*/
private final UserRepository userRepository;
@Autowired @Autowired
public UserService(@NonNull UserRepository userRepository) { public UserService() {
this.userRepository = userRepository; TimerUtils.scheduleRepeating(() -> {
long before = System.currentTimeMillis();
for (BatUser user : users.values()) {
user.save();
}
log.info("Saved {} users in {}ms", users.size(), System.currentTimeMillis() - before);
}, SAVE_INTERVAL, SAVE_INTERVAL);
} }
/** /**
* Gets a user by their ID * Gets a user by its ID
* *
* @param id The ID of the user * @param id The ID of the user
* @return The user * @return The user
*/ */
public BatUser getUser(@NonNull String id) { public BatUser getUser(@NonNull String id) {
long before = System.currentTimeMillis();
// User is cached
if (users.containsKey(id)) { if (users.containsKey(id)) {
return users.get(id); return users.get(id);
} }
// User is not cached
long start = System.currentTimeMillis(); Document document = MongoService.INSTANCE.getUsersCollection().find(Filters.eq("_id", id)).first();
Optional<BatUser> optionalUser = userRepository.findById(id); if (document != null) {
if (optionalUser.isPresent()) { BatUser user = new BatUser(id, document);
BatUser user = optionalUser.get();
users.put(id, user); users.put(id, user);
log.info("Loaded user \"{}\" in {}ms", user.getName(),System.currentTimeMillis() - before);
return user; return user;
} }
BatUser user = userRepository.save(new BatUser(id)); // New user
log.info("Created user for \"{}\" in {}ms", user.getDiscordUser().getName(), System.currentTimeMillis() - start); BatUser user = new BatUser(id, new Document());
users.put(id, user);
log.info("Created user \"{}\" - \"{}\"", user.getName(), user.getId());
return user; return user;
} }
/**
* Saves a user
*
* @param user The user to save
*/
public void saveUser(@NonNull BatUser user) {
userRepository.save(user);
}
} }