diff --git a/src/main/java/cc/fascinated/common/MathUtils.java b/src/main/java/cc/fascinated/common/MathUtils.java new file mode 100644 index 0000000..42b508a --- /dev/null +++ b/src/main/java/cc/fascinated/common/MathUtils.java @@ -0,0 +1,34 @@ +package cc.fascinated.common; + +import lombok.experimental.UtilityClass; + +/** + * @author Fascinated (fascinated7) + */ +@UtilityClass +public class MathUtils { + + /** + * Clamps a value between a minimum and maximum. + * + * @param value The value to clamp. + * @param min The minimum value. + * @param max The maximum value. + * @return The clamped value. + */ + public static double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + /** + * Linearly interpolates between two values. + * + * @param a The first value. + * @param b The second value. + * @param t The interpolation value. + * @return The interpolated value. + */ + public static double lerp(double a, double b, double t) { + return a + t * (b - a); + } +} diff --git a/src/main/java/cc/fascinated/model/score/TrackedScore.java b/src/main/java/cc/fascinated/model/score/TrackedScore.java index 9a223df..44c1bf5 100644 --- a/src/main/java/cc/fascinated/model/score/TrackedScore.java +++ b/src/main/java/cc/fascinated/model/score/TrackedScore.java @@ -2,13 +2,19 @@ package cc.fascinated.model.score; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.Getter; +import lombok.Setter; + +import java.util.Date; /** * @author Fascinated (fascinated7) */ @Entity @Getter +@Setter +@Table(name = "score") public class TrackedScore { /** * The ID of the score. @@ -86,6 +92,16 @@ public class TrackedScore { */ private String difficulty; + /** + * The star count of the difficulty. + */ + private Double stars; + + /** + * The timestamp of the score. + */ + private Date timestamp; + /** * Gets the Tracked Score as a DTO */ diff --git a/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardToken.java b/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardToken.java index 38e434e..2542645 100644 --- a/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardToken.java +++ b/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardToken.java @@ -2,14 +2,19 @@ package cc.fascinated.model.token; import lombok.Getter; import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; -@Getter @ToString +@Getter +@ToString +@Document("scoresaber_leaderboard") public class ScoreSaberLeaderboardToken { /** * The ID of the leaderboard. */ + @Id private String id; /** diff --git a/src/main/java/cc/fascinated/platform/CurvePoint.java b/src/main/java/cc/fascinated/platform/CurvePoint.java new file mode 100644 index 0000000..354a77c --- /dev/null +++ b/src/main/java/cc/fascinated/platform/CurvePoint.java @@ -0,0 +1,21 @@ +package cc.fascinated.platform; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author Fascinated (fascinated7) + */ +@AllArgsConstructor +@Getter +public class CurvePoint { + /** + * The x value of the curve point. + */ + private final double x; + + /** + * The y value of the curve point. + */ + private final double y; +} diff --git a/src/main/java/cc/fascinated/platform/Platform.java b/src/main/java/cc/fascinated/platform/Platform.java index 1959a64..213b075 100644 --- a/src/main/java/cc/fascinated/platform/Platform.java +++ b/src/main/java/cc/fascinated/platform/Platform.java @@ -3,18 +3,61 @@ package cc.fascinated.platform; import cc.fascinated.exception.impl.BadRequestException; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Map; /** * @author Fascinated (fascinated7) */ -@AllArgsConstructor +@RequiredArgsConstructor @Getter +@Setter public abstract class Platform { /** * The name of the platform. */ private final Platforms platform; + /** + * The previous curve version for this + * platform in the database. + */ + private int previousCurveVersion; + + /** + * The current curve version for getting + * pp from a star count. + */ + private final int currentCurveVersion; + + /** + * The curve points for each curve version. + */ + private final Map curvePoints; + + /** + * Checks the curve version to see if it exists. + * + * @param curveVersion the curve version to check + * @throws BadRequestException if the curve version does not exist + */ + public void checkCurveVersion(int curveVersion) { + if (!curvePoints.containsKey(curveVersion)) { + throw new BadRequestException("Curve version '%s' for platform '%s' was not found.".formatted(curveVersion, platform.getPlatformName())); + } + } + + /** + * Gets the PP amount from the star count. + * + * @param stars the amount of stars + * @return the pp amount + */ + public abstract double getPp(int curveVersion, double stars, double accuracy); + /** * Called every 10 minutes to update * the players data in QuestDB. @@ -27,6 +70,12 @@ public abstract class Platform { */ public abstract void updateMetrics(); + /** + * Called every day at midnight to update + * the leaderboards. + */ + public abstract void updateLeaderboards(); + @AllArgsConstructor @Getter public enum Platforms { diff --git a/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java b/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java index 9070fbf..534c902 100644 --- a/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java +++ b/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java @@ -1,8 +1,12 @@ package cc.fascinated.platform.impl; +import cc.fascinated.common.MathUtils; import cc.fascinated.model.score.TotalScoresResponse; +import cc.fascinated.model.score.TrackedScore; import cc.fascinated.model.token.ScoreSaberAccountToken; +import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; +import cc.fascinated.platform.CurvePoint; import cc.fascinated.platform.Platform; import cc.fascinated.services.QuestDBService; import cc.fascinated.services.ScoreSaberService; @@ -10,15 +14,35 @@ import cc.fascinated.services.TrackedScoreService; import cc.fascinated.services.UserService; import io.questdb.client.Sender; import lombok.NonNull; +import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Map; + /** * @author Fascinated (fascinated7) */ @Component +@Log4j2 public class ScoreSaberPlatform extends Platform { + /** + * Delay in ms for requests per minute. + */ + private static final long UPDATE_DELAY = 1000L / 250L; // 250 requests per minute + + /** + * The base multiplier for stars. + */ + private final double starMultiplier = 42.11; + + /** + * The weight coefficient for the platform. + */ + private final double weightCoefficient = 0.965; + /** * The ScoreSaber service to use */ @@ -46,13 +70,80 @@ public class ScoreSaberPlatform extends Platform { @Autowired public ScoreSaberPlatform(@NonNull ScoreSaberService scoreSaberService, @NonNull UserService userService, @NonNull QuestDBService questDBService, @NonNull TrackedScoreService trackedScoreService) { - super(Platforms.SCORESABER); + super(Platforms.SCORESABER, 1, Map.of( + 1, new CurvePoint[]{ + new CurvePoint(1.0, 5.367394282890631), + new CurvePoint(0.9995, 5.019543595874787), + new CurvePoint(0.999, 4.715470646416203), + new CurvePoint(0.99825, 4.325027383589547), + new CurvePoint(0.9975, 3.996793606763322), + new CurvePoint(0.99625, 3.5526145337555373), + new CurvePoint(0.995, 3.2022017597337955), + new CurvePoint(0.99375, 2.9190155639254955), + new CurvePoint(0.99125, 2.4902905794106913), + new CurvePoint(0.99, 2.324506282149922), + new CurvePoint(0.9875, 2.058947159052738), + new CurvePoint(0.985, 1.8563887693647105), + new CurvePoint(0.9825, 1.697536248647543), + new CurvePoint(0.98, 1.5702410055532239), + new CurvePoint(0.9775, 1.4664726399289512), + new CurvePoint(0.975, 1.3807102743105126), + new CurvePoint(0.9725, 1.3090333065057616), + new CurvePoint(0.97, 1.2485807759957321), + new CurvePoint(0.965, 1.1552120359501035), + new CurvePoint(0.96, 1.0871883573850478), + new CurvePoint(0.955, 1.0388633331418984), + new CurvePoint(0.95, 1.0), + new CurvePoint(0.94, 0.9417362980580238), + new CurvePoint(0.93, 0.9039994071865736), + new CurvePoint(0.92, 0.8728710341448851), + new CurvePoint(0.91, 0.8488375988124467), + new CurvePoint(0.9, 0.825756123560842), + new CurvePoint(0.875, 0.7816934560296046), + new CurvePoint(0.85, 0.7462290664143185), + new CurvePoint(0.825, 0.7150465663454271), + new CurvePoint(0.8, 0.6872268862950283), + new CurvePoint(0.75, 0.6451808210101443), + new CurvePoint(0.7, 0.6125565959114954), + new CurvePoint(0.65, 0.5866010012767576), + new CurvePoint(0.6, 0.18223233667439062), + new CurvePoint(0.0, 0.0) + } + )); this.scoreSaberService = scoreSaberService; this.userService = userService; this.questDBService = questDBService; this.trackedScoreService = trackedScoreService; } + /** + * Gets the modifier for the given accuracy. + * + * @param accuracy The accuracy. + * @return The modifier. + */ + public double getModifier(int curveVersion, double accuracy) { + accuracy = MathUtils.clamp(accuracy, 0, 100) / 100; + + CurvePoint prev = this.getCurvePoints().get(curveVersion)[0]; + for (CurvePoint point : this.getCurvePoints().get(curveVersion)) { + if (point.getX() >= accuracy) { + double distance = (prev.getX() - accuracy) / (prev.getX() - point.getX()); + return MathUtils.lerp(prev.getY(), point.getY(), distance); + } + prev = point; + } + return 0; + } + + @Override + public double getPp(int curveVersion, double stars, double accuracy) { + this.checkCurveVersion(curveVersion); // Check if the curve version exists + double pp = stars * this.starMultiplier; + double modifier = this.getModifier(curveVersion, accuracy); + return modifier * pp; + } + @Override public void updatePlayers() { for (User user : this.userService.getUsers()) { @@ -89,4 +180,56 @@ public class ScoreSaberPlatform extends Platform { .atNow(); } } + + @Override + public void updateLeaderboards() { + List scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), true); + List leaderboardIds = scores.stream().map(TrackedScore::getLeaderboardId).toList(); + + log.info("Updating {} leaderboards for platform '{}'", + leaderboardIds.size(), + this.getPlatform().getPlatformName() + ); + + int finished = 0; + + // Update the leaderboards + for (String id : leaderboardIds) { + try { + ScoreSaberLeaderboardToken leaderboard = this.scoreSaberService.getLeaderboard(id, true); // Update the cached leaderboard + if (leaderboard == null) { + log.warn("Failed to update leaderboard '{}' for platform '{}'", id, this.getPlatform().getPlatformName()); + continue; + } + + List toUpdate = scores.stream().filter(score -> score.getLeaderboardId().equals(id)).toList(); + for (TrackedScore score : toUpdate) { // Update the scores + double pp = this.getPp(this.getCurrentCurveVersion(), leaderboard.getStars(), score.getAccuracy()); + score.setPp(pp); + } + + if (!toUpdate.isEmpty()) { // Save the scores + this.trackedScoreService.updateScores(toUpdate.toArray(TrackedScore[]::new)); + } + finished++; + + log.info("Updated leaderboard '{}' for platform '{}', changed {} scores. ({}/{})", + id, + this.getPlatform().getPlatformName(), + toUpdate.size(), + finished, + leaderboardIds.size() + ); + + // Sleep to prevent rate limiting + try { + Thread.sleep(UPDATE_DELAY); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } catch (Exception ex) { + log.error("An error occurred while updating leaderboard '{}'", id, ex); + } + } + } } diff --git a/src/main/java/cc/fascinated/repository/couchdb/TrackedScoreRepository.java b/src/main/java/cc/fascinated/repository/couchdb/TrackedScoreRepository.java index 4153d36..8d1624f 100644 --- a/src/main/java/cc/fascinated/repository/couchdb/TrackedScoreRepository.java +++ b/src/main/java/cc/fascinated/repository/couchdb/TrackedScoreRepository.java @@ -1,6 +1,8 @@ package cc.fascinated.repository.couchdb; import cc.fascinated.model.score.TrackedScore; +import jakarta.transaction.Transactional; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -11,6 +13,16 @@ import java.util.List; * @author Fascinated (fascinated7) */ public interface TrackedScoreRepository extends CrudRepository { + /** + * Updates the pp of a score. + * + * @param scoreId the ID of the score + * @param pp the new pp of the score + */ + @Modifying @Transactional + @Query(value = "UPDATE score SET pp = :pp WHERE score_id = :scoreId", nativeQuery = true) + void updateScorePp(@Param("scoreId") String scoreId, @Param("pp") double pp); + /** * Gets a list of top tracked scores * sorted by pp from the platform @@ -22,6 +34,24 @@ public interface TrackedScoreRepository extends CrudRepository findTopRankedScores(@Param("platform") String platform, @Param("amount") int amount); + /** + * Gets all tracked scores from a platform. + * + * @param platform the platform to get the scores from + * @return the scores + */ + @Query(value = "SELECT * FROM score WHERE platform = :platform", nativeQuery = true) + List findAllByPlatform(String platform); + + /** + * Gets all tracked scores from a platform. + * + * @param platform the platform to get the scores from + * @return the scores + */ + @Query(value = "SELECT * FROM score WHERE platform = :platform AND pp > 0", nativeQuery = true) + List findAllByPlatformRankedOnly(String platform); + /** * Gets the total amount of scores * for a platform. diff --git a/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java b/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java new file mode 100644 index 0000000..fdb57e4 --- /dev/null +++ b/src/main/java/cc/fascinated/repository/mongo/ScoreSaberLeaderboardRepository.java @@ -0,0 +1,10 @@ +package cc.fascinated.repository.mongo; + +import cc.fascinated.model.token.ScoreSaberLeaderboardToken; +import org.springframework.data.mongodb.repository.MongoRepository; + +/** + * @author Fascinated (fascinated7) + */ +public interface ScoreSaberLeaderboardRepository extends MongoRepository { +} diff --git a/src/main/java/cc/fascinated/services/MongoService.java b/src/main/java/cc/fascinated/services/MongoService.java new file mode 100644 index 0000000..94278f6 --- /dev/null +++ b/src/main/java/cc/fascinated/services/MongoService.java @@ -0,0 +1,31 @@ +package cc.fascinated.services; + +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 mongoTemplate; + + @Autowired + public MongoService(MongoTemplate mongo) { + INSTANCE = this; + this.mongoTemplate = mongo; + } + + /** + * Get the platforms collection + * + * @return The platforms collection + */ + public MongoCollection getPlatformsCollection() { + return mongoTemplate.getCollection("platforms"); + } +} \ No newline at end of file diff --git a/src/main/java/cc/fascinated/services/PlatformService.java b/src/main/java/cc/fascinated/services/PlatformService.java index 412b78c..4439de9 100644 --- a/src/main/java/cc/fascinated/services/PlatformService.java +++ b/src/main/java/cc/fascinated/services/PlatformService.java @@ -1,9 +1,13 @@ package cc.fascinated.services; +import cc.fascinated.model.score.TrackedScore; import cc.fascinated.platform.Platform; import cc.fascinated.platform.impl.ScoreSaberPlatform; +import cc.fascinated.repository.couchdb.TrackedScoreRepository; +import com.mongodb.client.model.Filters; import lombok.NonNull; import lombok.extern.log4j.Log4j2; +import org.bson.Document; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.Scheduled; @@ -11,6 +15,8 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * @author Fascinated (fascinated7) @@ -18,24 +24,74 @@ import java.util.List; @Service @Log4j2(topic = "PlatformService") public class PlatformService { + private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(1); + /** * The loaded platforms. */ private final List platforms = new ArrayList<>(); + /** + * The tracked score repository to use. + */ + @NonNull + private final TrackedScoreRepository trackedScoreRepository; + @Autowired - public PlatformService(@NonNull ApplicationContext context) { + public PlatformService(@NonNull ApplicationContext context, @NonNull TrackedScoreRepository trackedScoreRepository) { + this.trackedScoreRepository = trackedScoreRepository; + + log.info("Registering platforms..."); registerPlatform(context.getBean(ScoreSaberPlatform.class)); + log.info("Loaded %s platforms.".formatted(this.platforms.size())); } - @Scheduled(cron = "0 */1 * * * *") - public void updatePlatforms() { - log.info("Updating %s platforms...".formatted(this.platforms.size())); + /** + * Updates the platform metrics. + *

+ * This method is scheduled to run every minute. + *

+ */ + @Scheduled(cron = "0 */5 * * * *") + public void updateMetrics() { + log.info("Updating %s platform metrics...".formatted(this.platforms.size())); for (Platform platform : this.platforms) { - platform.updatePlayers(); platform.updateMetrics(); } - log.info("Finished updating platforms."); + log.info("Finished updating platform metrics."); + } + + /** + * Updates the platform players. + *

+ * This method is scheduled to run every 15 minutes. + *

+ */ + @Scheduled(cron = "0 */15 * * * *") + public void updateScores() { + log.info("Updating %s platform players...".formatted(this.platforms.size())); + for (Platform platform : this.platforms) { + platform.updatePlayers(); + } + log.info("Finished updating platform players."); + } + + /** + * Refreshes the platform leaderboards. + *

+ * This method is scheduled to run every day at midnight. + * This is to ensure that the leaderboards are up-to-date. + *

+ */ + @Scheduled(cron = "0 0 0 * * *") + public void refreshPlatformLeaderboards() { + log.info("Refreshing platform leaderboards..."); + EXECUTOR_SERVICE.execute(() -> { + for (Platform platform : this.platforms) { + platform.updateLeaderboards(); + } + }); + log.info("Finished refreshing platform leaderboards."); } /** @@ -45,6 +101,32 @@ public class PlatformService { */ public void registerPlatform(Platform platform) { this.platforms.add(platform); + log.info(" - Registered platform '%s'".formatted(platform.getPlatform().getPlatformName())); + + // Find the platform in the database + Document document = MongoService.INSTANCE.getPlatformsCollection().find(Filters.eq("_id", platform.getPlatform().getPlatformName())).first(); + if (document == null) { // The platform was not found + document = new Document("_id", platform.getPlatform().getPlatformName()); + MongoService.INSTANCE.getPlatformsCollection().insertOne(document); + } + + // Set the previous curve version + platform.setPreviousCurveVersion(document.getInteger("currentCurveVersion", platform.getCurrentCurveVersion())); + + // The curve was updated + if (platform.getPreviousCurveVersion() != platform.getCurrentCurveVersion()) { + log.info(" - Updated previous curve version for platform '%s' to '%s'".formatted( + platform.getPlatform().getPlatformName(), + platform.getPreviousCurveVersion() + )); + + log.info("Updating scores for platform '%s'...".formatted(platform.getPlatform().getPlatformName())); + EXECUTOR_SERVICE.execute(platform::updateLeaderboards); // Update the leaderboards + } + + // Update the document + document.put("currentCurveVersion", platform.getCurrentCurveVersion()); + MongoService.INSTANCE.getPlatformsCollection().replaceOne(Filters.eq("_id", platform.getPlatform().getPlatformName()), document); } } diff --git a/src/main/java/cc/fascinated/services/ScoreSaberService.java b/src/main/java/cc/fascinated/services/ScoreSaberService.java index da954e9..6111584 100644 --- a/src/main/java/cc/fascinated/services/ScoreSaberService.java +++ b/src/main/java/cc/fascinated/services/ScoreSaberService.java @@ -2,11 +2,17 @@ package cc.fascinated.services; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.model.token.ScoreSaberAccountToken; +import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; +import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository; import kong.unirest.core.HttpResponse; import kong.unirest.core.Unirest; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Optional; + /** * @author Fascinated (fascinated7) */ @@ -14,9 +20,21 @@ import org.springframework.stereotype.Service; public class ScoreSaberService { 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_LEADERBOARD_ENDPOINT = SCORESABER_API + "leaderboard/by-id/%s/info"; /** - * Gets the ScoreSaber account for a user. + * The ScoreSaber leaderboard repository to use. + */ + @NonNull + private final ScoreSaberLeaderboardRepository leaderboardRepository; + + @Autowired + public ScoreSaberService(@NonNull ScoreSaberLeaderboardRepository leaderboardRepository) { + this.leaderboardRepository = leaderboardRepository; + } + + /** + * Gets the account for a user. * * @param user the user to get the account for * @return the ScoreSaber account @@ -29,13 +47,49 @@ public class ScoreSaberService { HttpResponse response = Unirest.get(GET_PLAYER_ENDPOINT.formatted(user.getSteamId())) .asObject(ScoreSaberAccountToken.class); - if (response.getParsingError().isPresent()) { // Failed to parse the response - throw new BadRequestException("Failed to parse ScoreSaber account for %s".formatted(user.getUsername())); + throw new BadRequestException("Failed to parse ScoreSaber account for '%s'".formatted(user.getUsername())); } if (response.getStatus() != 200) { // The response was not successful - throw new BadRequestException("Failed to get ScoreSaber account for %s".formatted(user.getUsername())); + throw new BadRequestException("Failed to get ScoreSaber account for '%s'".formatted(user.getUsername())); } return response.getBody(); } + + /** + * Gets a leaderboard for a leaderboard id. + * + * @param leaderboardId the leaderboard id to get the leaderboard for + * @return the ScoreSaber leaderboard + * @throws BadRequestException if an error occurred while getting the leaderboard + */ + public ScoreSaberLeaderboardToken getLeaderboard(String leaderboardId, boolean bypassCache) { + Optional leaderboardOptional = leaderboardRepository.findById(leaderboardId); + if (leaderboardOptional.isPresent() && !bypassCache) { // The leaderboard is cached + return leaderboardOptional.get(); + } + + HttpResponse response = Unirest.get(GET_LEADERBOARD_ENDPOINT.formatted(leaderboardId)) + .asObject(ScoreSaberLeaderboardToken.class); + if (response.getParsingError().isPresent()) { // Failed to parse the response + throw new BadRequestException("Failed to parse ScoreSaber leaderboard for '%s'".formatted(leaderboardId)); + } + if (response.getStatus() != 200) { // The response was not successful + throw new BadRequestException("Failed to get ScoreSaber leaderboard for '%s'".formatted(leaderboardId)); + } + ScoreSaberLeaderboardToken leaderboard = response.getBody(); + leaderboardRepository.save(leaderboard); + return leaderboard; + } + + /** + * Gets a leaderboard for a leaderboard id. + * + * @param leaderboardId the leaderboard id to get the leaderboard for + * @return the ScoreSaber leaderboard + * @throws BadRequestException if an error occurred while getting the leaderboard + */ + public ScoreSaberLeaderboardToken getLeaderboard(String leaderboardId) { + return getLeaderboard(leaderboardId, false); + } } diff --git a/src/main/java/cc/fascinated/services/TrackedScoreService.java b/src/main/java/cc/fascinated/services/TrackedScoreService.java index 3e36d9e..adca27f 100644 --- a/src/main/java/cc/fascinated/services/TrackedScoreService.java +++ b/src/main/java/cc/fascinated/services/TrackedScoreService.java @@ -26,7 +26,8 @@ public class TrackedScoreService { /** * The tracked score repository to use. */ - @NonNull private final TrackedScoreRepository trackedScoreRepository; + @NonNull + private final TrackedScoreRepository trackedScoreRepository; @Autowired public TrackedScoreService(@NonNull TrackedScoreRepository trackedScoreRepository) { @@ -38,7 +39,7 @@ public class TrackedScoreService { * sorted by pp from the platform * * @param platform the platform to get the scores from - * @param amount the amount of scores to get + * @param amount the amount of scores to get * @return the scores */ public List getTopScores(Platform.Platforms platform, int amount) { @@ -77,4 +78,29 @@ public class TrackedScoreService { trackedScoreRepository.countTotalRankedScores(platform.getPlatformName()) ); } + + /** + * Gets a list of tracked scores + * for a platform. + * + * @param platform the platform to get the scores from + * @return the tracked scores + */ + public List getTrackedScores(Platform.Platforms platform, boolean ranked) { + if (ranked) { + return trackedScoreRepository.findAllByPlatformRankedOnly(platform.getPlatformName()); + } + return trackedScoreRepository.findAllByPlatform(platform.getPlatformName()); + } + + /** + * Saves a list of tracked scores. + * + * @param scores the scores to save + */ + public void updateScores(TrackedScore... scores) { + for (TrackedScore score : scores) { + this.trackedScoreRepository.updateScorePp(score.getScoreId(), score.getPp()); + } + } }