add a basic event system to clean up scoresaber score receiving

This commit is contained in:
Lee 2024-06-25 10:40:17 +01:00
parent c0ae0fc596
commit aa6ab7d3d8
14 changed files with 222 additions and 116 deletions

@ -1,6 +1,5 @@
package cc.fascinated.bat.common; package cc.fascinated.bat.common;
import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
/** /**

@ -0,0 +1,20 @@
package cc.fascinated.bat.event;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberLeaderboardToken;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberScoreToken;
/**
* @author Fascinated (fascinated7)
*/
public interface EventListener {
/**
* Called when a ScoreSaber score is received
*
* @param score the score that was set
* @param leaderboard the leaderboard that the score was set on
* @param player the player that set the score
*/
default void onScoresaberScoreReceived(ScoreSaberPlayerScoreToken score, ScoreSaberLeaderboardToken leaderboard,
ScoreSaberScoreToken.LeaderboardPlayerInfo player) {}
}

@ -0,0 +1,92 @@
package cc.fascinated.bat.features.scoresaber;
import cc.fascinated.bat.common.Colors;
import cc.fascinated.bat.common.DateUtils;
import cc.fascinated.bat.common.NumberUtils;
import cc.fascinated.bat.common.ScoreSaberUtils;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberLeaderboardToken;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberPlayerScoreToken;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberScoreToken;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.guild.profiles.ScoreSaberUserScoreFeedProfile;
import cc.fascinated.bat.service.DiscordService;
import cc.fascinated.bat.service.GuildService;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class ScoreSaberScoreFeedListener implements EventListener {
private final GuildService guildService;
@Autowired
public ScoreSaberScoreFeedListener(GuildService guildService) {
this.guildService = guildService;
}
@Override
public void onScoresaberScoreReceived(ScoreSaberPlayerScoreToken score, ScoreSaberLeaderboardToken leaderboard,
ScoreSaberScoreToken.LeaderboardPlayerInfo player) {
for (Guild guild : DiscordService.JDA.getGuilds()) {
BatGuild batGuild = guildService.getGuild(guild.getId());
if (batGuild == null) {
continue;
}
ScoreSaberUserScoreFeedProfile profile = batGuild.getProfile(ScoreSaberUserScoreFeedProfile.class);
if (profile == null) {
continue;
}
if (profile.getChannelId() == null) {
continue;
}
if (!profile.getTrackedUsers().contains(player.getId())) {
continue;
}
profile.getAsTextChannel().sendMessageEmbeds(this.buildScoreEmbed(score)).queue();
}
}
/**
* Builds an embed for a score.
*
* @param score The score.
* @return The embed.
*/
public MessageEmbed buildScoreEmbed(ScoreSaberPlayerScoreToken score) {
ScoreSaberScoreToken scoreToken = score.getScore();
ScoreSaberLeaderboardToken leaderboardToken = score.getLeaderboard();
ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = scoreToken.getLeaderboardPlayerInfo();
return new EmbedBuilder()
.setAuthor(playerInfo.getName() + " just set a new score!", "https://scoresaber.com/u/%s".formatted(playerInfo.getId()),
"https://cdn.scoresaber.com/avatars/%s.jpg".formatted(playerInfo.getId()))
.setDescription("**%s** (%s%s)\n[[Map Link]](%s) [[SS Profile]](%s)".formatted(
leaderboardToken.getSongName(),
ScoreSaberUtils.getFormattedDifficulty(leaderboardToken.getDifficulty().getDifficulty()),
leaderboardToken.isRanked() ? " " + leaderboardToken.getStars() + "" : "",
"https://scoresaber.com/leaderboard/%s".formatted(leaderboardToken.getId()),
"https://scoresaber.com/u/%s".formatted(playerInfo.getId())
))
.addField("Accuracy", "%s%%".formatted(
leaderboardToken.getMaxScore() == 0 ? "N/A" : NumberUtils.formatNumberCommas(((double) scoreToken.getBaseScore() / leaderboardToken.getMaxScore()) * 100)
), true)
.addField("Raw PP", scoreToken.getPp() == 0 ? "Unranked" : NumberUtils.formatNumberCommas(scoreToken.getPp()), true)
.addField("Global Rank", "#%s".formatted(NumberUtils.formatNumberCommas(scoreToken.getRank())), true)
.addField("Misses", "%s".formatted(scoreToken.getMissedNotes()), true)
.addField("Bad Cuts", "%s".formatted(scoreToken.getBadCuts()), true)
.addField("Max Combo", "%s %s".formatted(
scoreToken.getMaxCombo(),
scoreToken.getMaxCombo() == leaderboardToken.getMaxScore() ? "(FC)" : ""
), true)
.setColor(Colors.DEFAULT)
.setTimestamp(DateUtils.getDateFromString(scoreToken.getTimeSet()).toInstant())
.build();
}
}

@ -1,9 +1,9 @@
package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.scoresaber;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken; import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile; import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
import cc.fascinated.bat.service.ScoreSaberService; import cc.fascinated.bat.service.ScoreSaberService;

@ -1,21 +1,19 @@
package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.scoresaber;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.common.Colors; import cc.fascinated.bat.common.Colors;
import cc.fascinated.bat.common.DateUtils; import cc.fascinated.bat.common.DateUtils;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.NumberUtils; import cc.fascinated.bat.common.NumberUtils;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken; import cc.fascinated.bat.model.beatsaber.scoresaber.ScoreSaberAccountToken;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile; import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
import cc.fascinated.bat.service.ScoreSaberService; import cc.fascinated.bat.service.ScoreSaberService;
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.entities.Member; import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
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 net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.OptionData;
@ -44,21 +42,9 @@ public class ScoreSaberCommand extends BatCommand {
.addSubcommands(new SubcommandData("link", "Link your ScoreSaber profile") .addSubcommands(new SubcommandData("link", "Link your ScoreSaber profile")
.addOptions(new OptionData(OptionType.STRING, "link", "Link your ScoreSaber profile", true)) .addOptions(new OptionData(OptionType.STRING, "link", "Link your ScoreSaber profile", true))
) )
.addSubcommands(new SubcommandData("user", "View a user's ScoreSaber profile") .addSubcommands(new SubcommandData("user", "View a user's ScoreSaber profile")
.addOptions(new OptionData(OptionType.USER, "user", "The user to view the ScoreSaber profile of", true)) .addOptions(new OptionData(OptionType.USER, "user", "The user to view the ScoreSaber profile of", true))
) )
.addSubcommands(new SubcommandData("score-feed-user", "Edit your ScoreSaber score feed settings")
.addOptions(new OptionData(OptionType.USER, "user", "Add or remove a user from the score feed", false))
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
.addSubcommands(new SubcommandData("score-feed-clear-users", "Remove all users from the score feed"))
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
.addSubcommands(new SubcommandData("score-feed-channel", "Edit your ScoreSaber score feed settings")
.addOptions(new OptionData(OptionType.CHANNEL, "channel", "Set the channel to send the score feed in", false))
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
); );
} }

@ -1,4 +1,4 @@
package cc.fascinated.bat.command.impl.global.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.scoresaber;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;

@ -1,10 +1,10 @@
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.userfeed;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.TextChannelUtils; import cc.fascinated.bat.common.TextChannelUtils;
import cc.fascinated.bat.model.guild.BatGuild; import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile; import cc.fascinated.bat.model.guild.profiles.ScoreSaberUserScoreFeedProfile;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import lombok.NonNull; import lombok.NonNull;
@ -31,7 +31,7 @@ public class ScoreFeedChannelCommand extends BatSubCommand {
@Override @Override
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) { public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class); ScoreSaberUserScoreFeedProfile profile = guild.getProfile(ScoreSaberUserScoreFeedProfile.class);
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())) {

@ -1,18 +1,15 @@
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.userfeed;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.model.guild.BatGuild; import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile; import cc.fascinated.bat.model.guild.profiles.ScoreSaberUserScoreFeedProfile;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
import cc.fascinated.bat.service.UserService; 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.User;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -31,7 +28,7 @@ public class ScoreFeedClearUsersCommand extends BatSubCommand {
@Override @Override
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) { public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class); ScoreSaberUserScoreFeedProfile profile = guild.getProfile(ScoreSaberUserScoreFeedProfile.class);
profile.getTrackedUsers().clear(); profile.getTrackedUsers().clear();
guildService.saveGuild(guild); guildService.saveGuild(guild);

@ -1,10 +1,9 @@
package cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber; package cc.fascinated.bat.features.scoresaber.command.userfeed;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.common.Profile;
import cc.fascinated.bat.model.guild.BatGuild; import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile; import cc.fascinated.bat.model.guild.profiles.ScoreSaberUserScoreFeedProfile;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile; import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
import cc.fascinated.bat.service.GuildService; import cc.fascinated.bat.service.GuildService;
@ -34,7 +33,7 @@ public class ScoreFeedUserCommand extends BatSubCommand {
@Override @Override
public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) { public void execute(@NonNull BatGuild guild, @NonNull BatUser user, @NonNull TextChannel channel, @NonNull Member member, @NonNull SlashCommandInteraction interaction) {
ScoreSaberScoreFeedProfile profile = guild.getProfile(ScoreSaberScoreFeedProfile.class); ScoreSaberUserScoreFeedProfile profile = guild.getProfile(ScoreSaberUserScoreFeedProfile.class);
OptionMapping option = interaction.getOption("user"); OptionMapping option = interaction.getOption("user");
if (option == null){ if (option == null){
if (profile.getTrackedUsers().isEmpty()) { if (profile.getTrackedUsers().isEmpty()) {

@ -0,0 +1,35 @@
package cc.fascinated.bat.features.scoresaber.command.userfeed;
import cc.fascinated.bat.command.BatCommand;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import org.springframework.stereotype.Component;
/**
* @author Fascinated (fascinated7)
*/
@Component
public class ScoreSaberUserFeedCommand extends BatCommand {
public ScoreSaberUserFeedCommand() {
super("scoresaber-userfeed");
super.setCategory(Category.BEAT_SABER);
super.setDescription("ScoreSaber user score feed commands");
super.setCommandData(new CommandDataImpl(this.getName(), this.getDescription())
.addSubcommands(new SubcommandData("user", "Edit your ScoreSaber score feed settings")
.addOptions(new OptionData(OptionType.USER, "user", "Add or remove a user from the score feed", false))
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
.addSubcommands(new SubcommandData("clear-users", "Remove all users from the score feed"))
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
.addSubcommands(new SubcommandData("channel", "Edit your ScoreSaber score feed settings")
.addOptions(new OptionData(OptionType.CHANNEL, "channel", "Set the channel to send the score feed in", false))
).setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MANAGE_SERVER))
);
}
}

@ -13,9 +13,9 @@ import java.util.List;
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@Getter @Setter @Getter @Setter
public class ScoreSaberScoreFeedProfile extends Profile { public class ScoreSaberUserScoreFeedProfile extends Profile {
public ScoreSaberScoreFeedProfile() { public ScoreSaberUserScoreFeedProfile() {
super("scoresaber-score-feed"); super("scoresaber-user-score-feed");
} }
/** /**

@ -2,13 +2,14 @@ package cc.fascinated.bat.service;
import cc.fascinated.bat.command.BatCommand; import cc.fascinated.bat.command.BatCommand;
import cc.fascinated.bat.command.BatSubCommand; import cc.fascinated.bat.command.BatSubCommand;
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.LinkSubCommand;
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.ScoreSaberCommand;
import cc.fascinated.bat.command.impl.global.beatsaber.scoresaber.UserSubCommand;
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedChannelCommand;
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedClearUsersCommand;
import cc.fascinated.bat.command.impl.guild.beatsaber.scoresaber.ScoreFeedUserCommand;
import cc.fascinated.bat.common.EmbedUtils; import cc.fascinated.bat.common.EmbedUtils;
import cc.fascinated.bat.features.scoresaber.command.scoresaber.LinkSubCommand;
import cc.fascinated.bat.features.scoresaber.command.scoresaber.ScoreSaberCommand;
import cc.fascinated.bat.features.scoresaber.command.scoresaber.UserSubCommand;
import cc.fascinated.bat.features.scoresaber.command.userfeed.ScoreFeedChannelCommand;
import cc.fascinated.bat.features.scoresaber.command.userfeed.ScoreFeedClearUsersCommand;
import cc.fascinated.bat.features.scoresaber.command.userfeed.ScoreFeedUserCommand;
import cc.fascinated.bat.features.scoresaber.command.userfeed.ScoreSaberUserFeedCommand;
import cc.fascinated.bat.model.guild.BatGuild; import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.user.BatUser; import cc.fascinated.bat.model.user.BatUser;
import lombok.NonNull; import lombok.NonNull;
@ -47,16 +48,10 @@ public class CommandService extends ListenerAdapter {
*/ */
private final UserService userService; private final UserService userService;
/**
* The application context to use
*/
private final ApplicationContext context;
@Autowired @Autowired
public CommandService(@NonNull GuildService guildService, @NonNull UserService userService, @NonNull ApplicationContext context) { public CommandService(@NonNull GuildService guildService, @NonNull UserService userService, @NonNull ApplicationContext context) {
this.guildService = guildService; this.guildService = guildService;
this.userService = userService; this.userService = userService;
this.context = context;
DiscordService.JDA.addEventListener(this); DiscordService.JDA.addEventListener(this);
// Guild commands // Guild commands
@ -66,9 +61,11 @@ public class CommandService extends ListenerAdapter {
registerCommand(context.getBean(ScoreSaberCommand.class) registerCommand(context.getBean(ScoreSaberCommand.class)
.addSubCommand("link", context.getBean(LinkSubCommand.class)) .addSubCommand("link", context.getBean(LinkSubCommand.class))
.addSubCommand("user", context.getBean(UserSubCommand.class)) .addSubCommand("user", context.getBean(UserSubCommand.class))
.addSubCommand("score-feed-user", context.getBean(ScoreFeedUserCommand.class)) );
.addSubCommand("score-feed-channel", context.getBean(ScoreFeedChannelCommand.class)) registerCommand(context.getBean(ScoreSaberUserFeedCommand.class)
.addSubCommand("score-feed-clear-users", context.getBean(ScoreFeedClearUsersCommand.class)) .addSubCommand("user", context.getBean(ScoreFeedUserCommand.class))
.addSubCommand("channel", context.getBean(ScoreFeedChannelCommand.class))
.addSubCommand("clear-users", context.getBean(ScoreFeedClearUsersCommand.class))
); );
registerSlashCommands(); // Register all slash commands registerSlashCommands(); // Register all slash commands

@ -0,0 +1,40 @@
package cc.fascinated.bat.service;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.features.scoresaber.ScoreSaberScoreFeedListener;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
/**
* @author Fascinated (fascinated7)
*/
@Service @Log4j2
public class EventService {
/**
* The list of listeners registered
*/
public static final Set<EventListener> LISTENERS = new HashSet<>();
@Autowired
public EventService(@NonNull ApplicationContext context) {
registerListeners(
context.getBean(ScoreSaberScoreFeedListener.class)
);
log.info("Registered {} listeners.", LISTENERS.size());
}
/**
* Registers an event listener
*
* @param listener the listener to register
*/
public void registerListeners(EventListener... listener) {
LISTENERS.addAll(Set.of(listener));
}
}

@ -1,20 +1,17 @@
package cc.fascinated.bat.service; package cc.fascinated.bat.service;
import cc.fascinated.bat.BatApplication; import cc.fascinated.bat.BatApplication;
import cc.fascinated.bat.common.*; import cc.fascinated.bat.common.DateUtils;
import cc.fascinated.bat.common.WebRequest;
import cc.fascinated.bat.event.EventListener;
import cc.fascinated.bat.exception.BadRequestException; import cc.fascinated.bat.exception.BadRequestException;
import cc.fascinated.bat.exception.ResourceNotFoundException; import cc.fascinated.bat.exception.ResourceNotFoundException;
import cc.fascinated.bat.model.beatsaber.scoresaber.*; import cc.fascinated.bat.model.beatsaber.scoresaber.*;
import cc.fascinated.bat.model.guild.BatGuild;
import cc.fascinated.bat.model.guild.profiles.ScoreSaberScoreFeedProfile;
import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile; import cc.fascinated.bat.model.user.profiles.ScoreSaberProfile;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.NonNull; import lombok.NonNull;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
@ -26,19 +23,15 @@ import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
@Service @Log4j2(topic = "ScoreSaber Service") @Service @Log4j2(topic = "ScoreSaber Service")
public class ScoreSaberService extends TextWebSocketHandler { public class ScoreSaberService extends TextWebSocketHandler {
private final GuildService guildService;
private static final String SCORESABER_API = "https://scoresaber.com/api/"; private static final String SCORESABER_API = "https://scoresaber.com/api/";
private static final String GET_PLAYER_ENDPOINT = SCORESABER_API + "player/%s/full"; private static final String GET_PLAYER_ENDPOINT = SCORESABER_API + "player/%s/full";
private static final String GET_PLAYER_SCORES_ENDPOINT = SCORESABER_API + "player/%s/scores?limit=100&sort=%s&page=%s&withMetadata=true"; private static final String GET_PLAYER_SCORES_ENDPOINT = SCORESABER_API + "player/%s/scores?limit=100&sort=%s&page=%s&withMetadata=true";
@Autowired @Autowired
public ScoreSaberService(@NonNull GuildService guildService) { public ScoreSaberService() {
this.guildService = guildService;
connectWebSocket(); connectWebSocket();
} }
@ -103,42 +96,6 @@ public class ScoreSaberService extends TextWebSocketHandler {
return scores; return scores;
} }
/**
* Builds an embed for a score.
*
* @param score The score.
* @return The embed.
*/
public MessageEmbed buildScoreEmbed(ScoreSaberPlayerScoreToken score) {
ScoreSaberScoreToken scoreToken = score.getScore();
ScoreSaberLeaderboardToken leaderboardToken = score.getLeaderboard();
ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = scoreToken.getLeaderboardPlayerInfo();
return new EmbedBuilder()
.setAuthor(playerInfo.getName() + " just set a new score!", "https://scoresaber.com/u/%s".formatted(playerInfo.getId()),
"https://cdn.scoresaber.com/avatars/%s.jpg".formatted(playerInfo.getId()))
.setDescription("**%s** (%s%s)\n[[Map Link]](%s) [[SS Profile]](%s)".formatted(
leaderboardToken.getSongName(),
ScoreSaberUtils.getFormattedDifficulty(leaderboardToken.getDifficulty().getDifficulty()),
leaderboardToken.isRanked() ? " " + leaderboardToken.getStars() + "" : "",
"https://scoresaber.com/leaderboard/%s".formatted(leaderboardToken.getId()),
"https://scoresaber.com/u/%s".formatted(playerInfo.getId())
))
.addField("Accuracy", "%s%%".formatted(
leaderboardToken.getMaxScore() == 0 ? "N/A" : NumberUtils.formatNumberCommas(((double) scoreToken.getBaseScore() / leaderboardToken.getMaxScore()) * 100)
), true)
.addField("Raw PP", scoreToken.getPp() == 0 ? "Unranked" : NumberUtils.formatNumberCommas(scoreToken.getPp()), true)
.addField("Global Rank", "#%s".formatted(NumberUtils.formatNumberCommas(scoreToken.getRank())), true)
.addField("Misses", "%s".formatted(scoreToken.getMissedNotes()), true)
.addField("Bad Cuts", "%s".formatted(scoreToken.getBadCuts()), true)
.addField("Max Combo", "%s %s".formatted(
scoreToken.getMaxCombo(),
scoreToken.getMaxCombo() == leaderboardToken.getMaxScore() ? "(FC)" : ""
), true)
.setColor(Colors.DEFAULT)
.setTimestamp(DateUtils.getDateFromString(scoreToken.getTimeSet()).toInstant())
.build();
}
/** /**
* Connects to the ScoreSaber WebSocket. * Connects to the ScoreSaber WebSocket.
*/ */
@ -175,24 +132,8 @@ public class ScoreSaberService extends TextWebSocketHandler {
ScoreSaberPlayerScoreToken score = BatApplication.GSON.fromJson(data, ScoreSaberPlayerScoreToken.class); ScoreSaberPlayerScoreToken score = BatApplication.GSON.fromJson(data, ScoreSaberPlayerScoreToken.class);
ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = score.getScore().getLeaderboardPlayerInfo(); ScoreSaberScoreToken.LeaderboardPlayerInfo playerInfo = score.getScore().getLeaderboardPlayerInfo();
for (Guild guild : DiscordService.JDA.getGuilds()) { for (EventListener listener : EventService.LISTENERS) {
BatGuild batGuild = guildService.getGuild(guild.getId()); listener.onScoresaberScoreReceived(score, score.getLeaderboard(), playerInfo);
if (batGuild == null) {
continue;
}
ScoreSaberScoreFeedProfile profile = batGuild.getProfile(ScoreSaberScoreFeedProfile.class);
if (profile == null) {
continue;
}
if (profile.getChannelId() == null) {
continue;
}
if (!profile.getTrackedUsers().contains(playerInfo.getId())) {
continue;
}
profile.getAsTextChannel().sendMessageEmbeds(this.buildScoreEmbed(score)).queue();
} }
} }
} catch (Exception ex) { } catch (Exception ex) {