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

@ -27,7 +27,7 @@ import java.util.*;
* @author Fascinated (fascinated7)
*/
@Service
@Log4j2
@Log4j2(topic = "Command Service")
@Getter
@DependsOn("discordService")
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.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);
}

View File

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

View File

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

View File

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

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

View File

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