cleanup ranked score updating
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 50s
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 50s
This commit is contained in:
parent
1d4e19bd3c
commit
4e828f2c2b
@ -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;
|
||||
}
|
@ -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<TrackedScore> scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), false);
|
||||
List<String> uniqueLeaderboardIds = scores.stream().map(TrackedScore::getLeaderboardId).distinct().toList();
|
||||
|
||||
// TODO: PUSH THIS
|
||||
List<TrackedScore> scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), true);
|
||||
Map<String, ScoreSaberLeaderboardToken> 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<String, ScoreSaberLeaderboardToken> 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<TrackedScore> 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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ScoreSaberLeaderboardPageToken> getRankedLeaderboards() {
|
||||
log.info("Getting all ranked leaderboards...");
|
||||
List<ScoreSaberLeaderboardPageToken> 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<ScoreSaberLeaderboardPageToken> 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.
|
||||
*
|
||||
|
@ -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())
|
||||
|
Reference in New Issue
Block a user