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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) ? "" :

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

View File

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

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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