big ass refactor to handle loading guilds and users without spring to make it more futureproof
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 44s

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

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

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

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

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

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

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

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

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

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

@ -1,11 +1,14 @@
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.service.DiscordService;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.dv8tion.jda.api.entities.Role;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
@ -15,7 +18,8 @@ import java.util.List;
*/
@Setter
@Getter
public class AutoRoleProfile extends Profile {
@NoArgsConstructor
public class AutoRoleProfile extends Serializable {
private static final int DEFAULT_MAX_ROLES = 10;
private static final int PREMIUM_MAX_ROLES = 25;
@ -24,10 +28,6 @@ public class AutoRoleProfile extends Profile {
*/
private List<String> roleIds;
public AutoRoleProfile() {
super("auto-role");
}
/**
* 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
*/
public static int getMaxRoleSlots(BatGuild guild) {
if (guild.getPremium().hasPremium()) {
if (guild.getPremiumProfile().hasPremium()) {
return PREMIUM_MAX_ROLES;
}
return DEFAULT_MAX_ROLES;
@ -110,4 +110,16 @@ public class AutoRoleProfile extends Profile {
public void reset() {
roleIds.clear();
}
@Override
public void load(Document document, Gson gson) {
roleIds = document.getList("roleIds", String.class);
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("roleIds", roleIds);
return document;
}
}

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

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

@ -5,6 +5,7 @@ import cc.fascinated.bat.command.CommandInfo;
import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.premium.PremiumProfile;
import lombok.NonNull;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
@ -21,7 +22,7 @@ import org.springframework.stereotype.Component;
public class PremiumCommand extends BatCommand {
@Override
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");
if (premium.hasPremium()) {
embed.addField("Premium", premium.hasPremium() ? "Yes" : "No", true);

@ -8,7 +8,6 @@ import cc.fascinated.bat.features.base.profile.FeatureProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.FeatureService;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
@ -24,11 +23,8 @@ import org.springframework.stereotype.Component;
@Component("feature:disable.sub")
@CommandInfo(name = "disable", description = "Disables a feature")
public class DisableSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public DisableSubCommand(@NonNull GuildService guildService) {
this.guildService = guildService;
public DisableSubCommand() {
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);
if (featureProfile.isFeatureDisabled(feature)) {
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();
return;
}
featureProfile.disableFeature(feature);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("Successfully enabled the `%s` feature".formatted(feature.getName()))
.setDescription("Successfully disabled the `%s` feature".formatted(feature.getName()))
.build()).queue();
}
}

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

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

@ -31,9 +31,8 @@ public class BirthdayFeature extends Feature implements EventListener {
@Override
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());
guildService.saveGuild(guild);
}
/**
@ -41,8 +40,8 @@ public class BirthdayFeature extends Feature implements EventListener {
*/
@Scheduled(cron = "0 1 0 * * *")
private void checkBirthdays() {
for (BatGuild guild : guildService.getAllGuilds()) {
BirthdayProfile profile = guild.getProfile(BirthdayProfile.class);
for (BatGuild guild : guildService.getGuilds().values()) {
BirthdayProfile profile = guild.getBirthdayProfile();
profile.checkBirthdays(guild);
}
}

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

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

@ -6,7 +6,6 @@ import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
@ -24,17 +23,14 @@ import org.springframework.stereotype.Component;
@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)
public class MessageSubCommand extends BatSubCommand {
private final GuildService guildService;
@Autowired
public MessageSubCommand(GuildService guildService) {
public MessageSubCommand() {
super.addOption(OptionType.STRING, "message", "The message that is sent. (Placeholders: {user}, {age})", true);
this.guildService = guildService;
}
@Override
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");
if (messageOption == null) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
@ -58,7 +54,6 @@ public class MessageSubCommand extends BatSubCommand {
}
profile.setMessage(message);
guildService.saveGuild(guild);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated the birthday message!\n\n**Message:** %s".formatted(profile.getBirthdayMessage(user.getDiscordUser())))
.build()).queue();

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

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

@ -7,7 +7,6 @@ import cc.fascinated.bat.features.birthday.UserBirthday;
import cc.fascinated.bat.features.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.model.BatGuild;
import cc.fascinated.bat.model.BatUser;
import cc.fascinated.bat.service.GuildService;
import lombok.NonNull;
import net.dv8tion.jda.api.entities.Member;
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")
public class SetSubCommand extends BatSubCommand {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy");
private final GuildService guildService;
@Autowired
public SetSubCommand(GuildService guildService) {
public SetSubCommand() {
super.addOption(OptionType.STRING, "birthday", "Your birthday (format: DAY/MONTH/YEAR - 01/05/2004)", true);
this.guildService = guildService;
}
@Override
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()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.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;
}
profile.addBirthday(member.getId(), new UserBirthday(birthday, false));
guildService.saveGuild(guild);
UserBirthday userBirthday = new UserBirthday();
userBirthday.setBirthday(birthday);
userBirthday.setHidden(false);
profile.addBirthday(member.getId(), userBirthday);
interaction.replyEmbeds(EmbedUtils.successEmbed()
.setDescription("You have updated your birthday!")
.build()).queue();

@ -34,7 +34,7 @@ public class ViewSubCommand extends BatSubCommand {
@Override
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()) {
interaction.replyEmbeds(EmbedUtils.errorEmbed()
.setDescription("Birthdays have not been enabled in this guild. Please ask an administrator to enable them.")

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

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

@ -1,22 +1,42 @@
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.Setter;
import org.bson.Document;
import java.util.Date;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor @Getter
public class TrackedName {
@Getter @Setter
public class TrackedName extends Serializable {
/**
* The new name of the user
*/
private final String name;
private String name;
/**
* The date the name was changed
*/
private final Date changedDate;
private Date changedDate;
@Override
public void load(Document document, Gson gson) {
this.name = document.getString("name");
this.changedDate = document.getDate("changedDate");
}
@Override
public Document serialize(Gson gson) {
Document document = new Document();
document.put("name", this.name);
document.put("changedDate", this.changedDate);
return document;
}
@Override
public void reset() {}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,25 +1,41 @@
package cc.fascinated.bat.model;
import cc.fascinated.bat.BatApplication;
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.birthday.profile.BirthdayProfile;
import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile;
import cc.fascinated.bat.premium.PremiumProfile;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
*/
@RequiredArgsConstructor
@Getter
@Setter
@Document(collection = "guilds")
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
*/
@ -30,23 +46,13 @@ public class BatGuild extends ProfileHolder {
/**
* The time this guild was joined
*/
private Date createdAt = new Date();
private Date createdAt;
/**
* The premium information for the guild
*/
private Premium premium;
/**
* 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;
public BatGuild(@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");
}
/**
@ -85,91 +91,46 @@ public class BatGuild extends ProfileHolder {
return getProfile(FeatureProfile.class);
}
@AllArgsConstructor
@Getter
@Setter
public static class Premium {
/**
* The time the premium was activated
*/
private Date activatedAt;
/**
* Gets the premium profile
*
* @return the premium profile
*/
public PremiumProfile getPremiumProfile() {
return getProfile(PremiumProfile.class);
}
/**
* The time the premium expires
*/
private Date expiresAt;
/**
* Gets the birthday profile
*
* @return the birthday profile
*/
public BirthdayProfile getBirthdayProfile() {
return getProfile(BirthdayProfile.class);
}
/**
* The type of premium
*/
private Type type;
/**
* Saves the user
*/
public void save() {
document.put("_id", id);
document.put("createdAt", createdAt);
/**
* 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()));
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);
/**
* 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;
}
MongoService.INSTANCE.getGuildsCollection().replaceOne(
new org.bson.Document("_id", id),
this.getDocument(),
new ReplaceOptions().upsert(true)
);
}
/**
* 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;
}
/**
* The premium type for the guild
*/
public enum Type {
INFINITE,
MONTHLY
}
@Override
public <T extends Serializable> T getProfile(Class<T> clazz) {
return getProfileFromDocument(clazz, document);
}
}

@ -1,18 +1,26 @@
package cc.fascinated.bat.model;
import cc.fascinated.bat.BatApplication;
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.scoresaber.profile.user.ScoreSaberProfile;
import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.MongoService;
import com.mongodb.client.model.ReplaceOptions;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
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.mongodb.core.mapping.Document;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author Fascinated (fascinated7)
@ -22,6 +30,12 @@ import java.util.Date;
@Setter
@Document(collection = "users")
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
*/
@ -32,7 +46,14 @@ public class BatUser extends ProfileHolder {
/**
* 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
@ -67,4 +88,29 @@ public class BatUser extends ProfileHolder {
public NameHistoryProfile getNameHistoryProfile() {
return getProfile(NameHistoryProfile.class);
}
/**
* Saves the user
*/
public void save() {
document.put("_id", id);
document.put("createdAt", createdAt);
Map<String, org.bson.Document> profileDocuments = new HashMap<>();
for (Serializable profile : getProfiles().values()) {
profileDocuments.put(profile.getClass().getSimpleName(), profile.serialize(BatApplication.GSON));
}
document.put("profiles", profileDocuments);
MongoService.INSTANCE.getUsersCollection().replaceOne(
new org.bson.Document("_id", id),
this.getDocument(),
new ReplaceOptions().upsert(true)
);
}
@Override
public <T extends Serializable> T getProfile(Class<T> clazz) {
return getProfileFromDocument(clazz, document);
}
}

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

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

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

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

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

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

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

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

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

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

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