From 4e828f2c2b02b78323e5fea29c6bbe524b56291f Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 1 Aug 2024 02:00:47 +0100 Subject: [PATCH] cleanup ranked score updating --- .../token/ScoreSaberLeaderboardPageToken.java | 19 ++++++ .../platform/impl/ScoreSaberPlatform.java | 60 +++++++++++-------- .../fascinated/services/PlatformService.java | 28 +++++++-- .../services/ScoreSaberService.java | 49 +++++++++++++++ .../websocket/impl/ScoreSaberWebsocket.java | 14 ++++- 5 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardPageToken.java diff --git a/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardPageToken.java b/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardPageToken.java new file mode 100644 index 0000000..b305dbe --- /dev/null +++ b/src/main/java/cc/fascinated/model/token/ScoreSaberLeaderboardPageToken.java @@ -0,0 +1,19 @@ +package cc.fascinated.model.token; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter @Setter +@ToString +public class ScoreSaberLeaderboardPageToken { + /** + * The scores on this page. + */ + private ScoreSaberLeaderboardToken[] leaderboards; + + /** + * The metadata for this page. + */ + private ScoreSaberPageMetadataToken metadata; +} diff --git a/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java b/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java index 2b285fa..2c26d2f 100644 --- a/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java +++ b/src/main/java/cc/fascinated/platform/impl/ScoreSaberPlatform.java @@ -4,6 +4,7 @@ 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.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; import cc.fascinated.platform.CurvePoint; @@ -18,6 +19,8 @@ import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -187,42 +190,53 @@ public class ScoreSaberPlatform extends Platform { @Override public void updateLeaderboards() { - List scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), false); - List uniqueLeaderboardIds = scores.stream().map(TrackedScore::getLeaderboardId).distinct().toList(); - + // TODO: PUSH THIS + List scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), true); + Map rankedLeaderboards = new HashMap<>(); + for (ScoreSaberLeaderboardPageToken rankedLeaderboard : this.scoreSaberService.getRankedLeaderboards()) { + for (ScoreSaberLeaderboardToken leaderboard : rankedLeaderboard.getLeaderboards()) { + rankedLeaderboards.put(leaderboard.getId(), leaderboard); + } + } log.info("Updating {} leaderboards for platform '{}'", - uniqueLeaderboardIds.size(), + rankedLeaderboards.size(), this.getPlatform().getPlatformName() ); // Update the leaderboards int finished = 0; - for (String id : uniqueLeaderboardIds) { + + for (Map.Entry leaderboardEntry : rankedLeaderboards.entrySet()) { + String id = leaderboardEntry.getKey(); + ScoreSaberLeaderboardToken leaderboard = leaderboardEntry.getValue(); + + if (finished > 0) { + // Sleep to prevent rate limiting + try { + Thread.sleep(UPDATE_DELAY); + } catch (InterruptedException e) { + log.error("Failed to sleep for rate limit reset", e); + } + } + 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; } - if (leaderboard.getStars() == 0) { // Check if the leaderboard is ranked - log.warn("Leaderboard '{}' for platform '{}' is not ranked, skipping updating the score pp values", - id, - this.getPlatform().getPlatformName() - ); - continue; - } List toUpdate = scores.stream().filter(score -> { if (!score.getLeaderboardId().equals(id)) { // Check if the leaderboard ID matches return false; } - double oldPp = score.getPp() == null ? 0 : score.getPp(); double pp = this.getPp(leaderboard.getStars(), score.getAccuracy()); - log.info("Leaderboard: {}, Old PP: {}, New PP: {}", leaderboard.getSongName(), oldPp, pp); - return pp != oldPp; // Check if the pp has changed + return pp != (score.getPp() == null ? 0D : score.getPp()); // Check if the pp has changed }).toList(); for (TrackedScore score : toUpdate) { // Update the scores + if (leaderboard.getStars() == 0) { // The leaderboard was unranked + score.setPp(0D); + } double pp = this.getPp(leaderboard.getStars(), score.getAccuracy()); score.setPp(pp); } @@ -233,19 +247,13 @@ public class ScoreSaberPlatform extends Platform { finished++; log.info("Updated leaderboard '{}' for platform '{}', changed {} scores. ({}/{})", - id, + leaderboard.getSongName(), this.getPlatform().getPlatformName(), toUpdate.size(), - finished, - uniqueLeaderboardIds.size() - ); - // Sleep to prevent rate limiting - try { - Thread.sleep(UPDATE_DELAY); - } catch (InterruptedException e) { - log.error("Failed to sleep for rate limit reset", e); - } + finished, + rankedLeaderboards.size() + ); } catch (Exception ex) { log.error("An error occurred while updating leaderboard '{}'", id, ex); } diff --git a/src/main/java/cc/fascinated/services/PlatformService.java b/src/main/java/cc/fascinated/services/PlatformService.java index 090a9c6..4350750 100644 --- a/src/main/java/cc/fascinated/services/PlatformService.java +++ b/src/main/java/cc/fascinated/services/PlatformService.java @@ -120,12 +120,32 @@ public class PlatformService { )); log.info("Updating scores for platform '%s'...".formatted(platform.getPlatform().getPlatformName())); - EXECUTOR_SERVICE.execute(platform::updateLeaderboards); // Update the leaderboards + Document finalDocument = document; + EXECUTOR_SERVICE.execute(() -> { + platform.updateLeaderboards(); + // Update the document + finalDocument.put("currentCurveVersion", platform.getCurrentCurveVersion()); + MongoService.INSTANCE.getPlatformsCollection().replaceOne(Filters.eq("_id", platform.getPlatform().getPlatformName()), finalDocument); + }); // Update the leaderboards + } else { + // Update the document + document.put("currentCurveVersion", platform.getCurrentCurveVersion()); + MongoService.INSTANCE.getPlatformsCollection().replaceOne(Filters.eq("_id", platform.getPlatform().getPlatformName()), document); } + } - // Update the document - document.put("currentCurveVersion", platform.getCurrentCurveVersion()); - MongoService.INSTANCE.getPlatformsCollection().replaceOne(Filters.eq("_id", platform.getPlatform().getPlatformName()), document); + /** + * Gets the ScoreSaber platform. + * + * @return the ScoreSaber platform + */ + public ScoreSaberPlatform getScoreSaberPlatform() { + for (Platform platform : this.platforms) { + if (platform.getPlatform().getPlatformName().equalsIgnoreCase(Platform.Platforms.SCORESABER.getPlatformName())) { + return (ScoreSaberPlatform) platform; + } + } + return null; } } diff --git a/src/main/java/cc/fascinated/services/ScoreSaberService.java b/src/main/java/cc/fascinated/services/ScoreSaberService.java index a57e532..77e9033 100644 --- a/src/main/java/cc/fascinated/services/ScoreSaberService.java +++ b/src/main/java/cc/fascinated/services/ScoreSaberService.java @@ -3,24 +3,32 @@ package cc.fascinated.services; import cc.fascinated.common.Request; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.model.token.ScoreSaberAccountToken; +import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.user.User; import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository; import kong.unirest.core.HttpResponse; import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; /** * @author Fascinated (fascinated7) */ @Service +@Log4j2(topic = "ScoreSaber 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"; + private static final String GET_RANKED_LEADERBOARDS_ENDPOINT = SCORESABER_API + "leaderboards?ranked=true&page=%s&withMetadata=true"; /** * The ScoreSaber leaderboard repository to use. @@ -80,6 +88,47 @@ public class ScoreSaberService { return leaderboard; } + /** + * Gets a list of all the ranked leaderboards. + * + * @return the ranked leaderboards + * @throws BadRequestException if an error occurred while getting the leaderboards + */ + public List getRankedLeaderboards() { + log.info("Getting all ranked leaderboards..."); + List pages = new LinkedList<>(); + int page = 1; + do { + log.info("Getting ranked leaderboard page '%s'...".formatted(page)); + ScoreSaberLeaderboardPageToken pageToken = getRankedLeaderboards(page); + pages.add(pageToken); + for (ScoreSaberLeaderboardToken leaderboard : pageToken.getLeaderboards()) { + this.leaderboardRepository.save(leaderboard); + } + page++; + } while (page <= ((pages.get(0).getMetadata().getTotal() / pages.get(0).getMetadata().getItemsPerPage()) + 1)); + log.info("Finished getting all ranked leaderboards, found '{}' pages.", pages.size()); + return pages; + } + + /** + * Gets a list of the ranked leaderboards for a page. + * + * @param page the page to get the leaderboards for + * @return the ranked leaderboards + * @throws BadRequestException if an error occurred while getting the leaderboards + */ + public ScoreSaberLeaderboardPageToken getRankedLeaderboards(int page) { + HttpResponse response = Request.get(GET_RANKED_LEADERBOARDS_ENDPOINT.formatted(page), ScoreSaberLeaderboardPageToken.class); + if (response.getParsingError().isPresent()) { // Failed to parse the response + throw new BadRequestException("Failed to parse ScoreSaber leaderboard page for page '%s'".formatted(page)); + } + if (response.getStatus() != 200) { // The response was not successful + throw new BadRequestException("Failed to get ScoreSaber leaderboard page for page '%s'".formatted(page)); + } + return response.getBody(); + } + /** * Gets a leaderboard for a leaderboard id. * diff --git a/src/main/java/cc/fascinated/websocket/impl/ScoreSaberWebsocket.java b/src/main/java/cc/fascinated/websocket/impl/ScoreSaberWebsocket.java index ebeaac8..12e64ed 100644 --- a/src/main/java/cc/fascinated/websocket/impl/ScoreSaberWebsocket.java +++ b/src/main/java/cc/fascinated/websocket/impl/ScoreSaberWebsocket.java @@ -6,6 +6,8 @@ import cc.fascinated.model.token.ScoreSaberPlayerScoreToken; import cc.fascinated.model.token.ScoreSaberScoreToken; import cc.fascinated.model.token.ScoreSaberWebsocketDataToken; import cc.fascinated.platform.Platform; +import cc.fascinated.platform.impl.ScoreSaberPlatform; +import cc.fascinated.services.PlatformService; import cc.fascinated.services.QuestDBService; import cc.fascinated.services.UserService; import cc.fascinated.websocket.Websocket; @@ -39,12 +41,19 @@ public class ScoreSaberWebsocket extends Websocket { */ private final QuestDBService questDBService; + /** + * The platform service to use + */ + private final PlatformService platformService; + @Autowired - public ScoreSaberWebsocket(@NonNull ObjectMapper objectMapper, @NonNull UserService userService, @NonNull QuestDBService questDBService) { + public ScoreSaberWebsocket(@NonNull ObjectMapper objectMapper, @NonNull UserService userService, @NonNull QuestDBService questDBService, + @NonNull PlatformService platformService) { super("ScoreSaber", "wss://scoresaber.com/ws"); this.objectMapper = objectMapper; this.userService = userService; this.questDBService = questDBService; + this.platformService = platformService; } @Override @SneakyThrows @@ -71,6 +80,7 @@ public class ScoreSaberWebsocket extends Websocket { double accuracy = ((double) score.getBaseScore() / leaderboard.getMaxScore()) * 100; String difficulty = ScoreSaberUtils.parseDifficulty(leaderboard.getDifficulty().getDifficulty()); + double pp = platformService.getScoreSaberPlatform().getPp(leaderboard.getStars(), accuracy); // Recalculate the PP try (Sender sender = questDBService.getSender()) { sender.table("score") @@ -80,7 +90,7 @@ public class ScoreSaberWebsocket extends Websocket { // Score information .symbol("leaderboard_id", leaderboard.getId()) .symbol("score_id", score.getId()) - .doubleColumn("pp", score.getPp()) + .doubleColumn("pp", pp) .longColumn("rank", score.getRank()) .longColumn("score", score.getBaseScore()) .longColumn("modified_score", score.getModifiedScore())