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.TotalScoresResponse;
|
||||||
import cc.fascinated.model.score.TrackedScore;
|
import cc.fascinated.model.score.TrackedScore;
|
||||||
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
||||||
|
import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken;
|
||||||
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
|
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
|
||||||
import cc.fascinated.model.user.User;
|
import cc.fascinated.model.user.User;
|
||||||
import cc.fascinated.platform.CurvePoint;
|
import cc.fascinated.platform.CurvePoint;
|
||||||
@ -18,6 +19,8 @@ import lombok.extern.log4j.Log4j2;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -187,42 +190,53 @@ public class ScoreSaberPlatform extends Platform {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateLeaderboards() {
|
public void updateLeaderboards() {
|
||||||
List<TrackedScore> scores = this.trackedScoreService.getTrackedScores(this.getPlatform(), false);
|
// TODO: PUSH THIS
|
||||||
List<String> uniqueLeaderboardIds = scores.stream().map(TrackedScore::getLeaderboardId).distinct().toList();
|
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 '{}'",
|
log.info("Updating {} leaderboards for platform '{}'",
|
||||||
uniqueLeaderboardIds.size(),
|
rankedLeaderboards.size(),
|
||||||
this.getPlatform().getPlatformName()
|
this.getPlatform().getPlatformName()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the leaderboards
|
// Update the leaderboards
|
||||||
int finished = 0;
|
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 {
|
try {
|
||||||
ScoreSaberLeaderboardToken leaderboard = this.scoreSaberService.getLeaderboard(id, true); // Update the cached leaderboard
|
|
||||||
if (leaderboard == null) {
|
if (leaderboard == null) {
|
||||||
log.warn("Failed to update leaderboard '{}' for platform '{}'", id, this.getPlatform().getPlatformName());
|
log.warn("Failed to update leaderboard '{}' for platform '{}'", id, this.getPlatform().getPlatformName());
|
||||||
continue;
|
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 -> {
|
List<TrackedScore> toUpdate = scores.stream().filter(score -> {
|
||||||
if (!score.getLeaderboardId().equals(id)) { // Check if the leaderboard ID matches
|
if (!score.getLeaderboardId().equals(id)) { // Check if the leaderboard ID matches
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
double oldPp = score.getPp() == null ? 0 : score.getPp();
|
|
||||||
double pp = this.getPp(leaderboard.getStars(), score.getAccuracy());
|
double pp = this.getPp(leaderboard.getStars(), score.getAccuracy());
|
||||||
log.info("Leaderboard: {}, Old PP: {}, New PP: {}", leaderboard.getSongName(), oldPp, pp);
|
return pp != (score.getPp() == null ? 0D : score.getPp()); // Check if the pp has changed
|
||||||
return pp != oldPp; // Check if the pp has changed
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
for (TrackedScore score : toUpdate) { // Update the scores
|
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());
|
double pp = this.getPp(leaderboard.getStars(), score.getAccuracy());
|
||||||
score.setPp(pp);
|
score.setPp(pp);
|
||||||
}
|
}
|
||||||
@ -233,19 +247,13 @@ public class ScoreSaberPlatform extends Platform {
|
|||||||
finished++;
|
finished++;
|
||||||
|
|
||||||
log.info("Updated leaderboard '{}' for platform '{}', changed {} scores. ({}/{})",
|
log.info("Updated leaderboard '{}' for platform '{}', changed {} scores. ({}/{})",
|
||||||
id,
|
leaderboard.getSongName(),
|
||||||
this.getPlatform().getPlatformName(),
|
this.getPlatform().getPlatformName(),
|
||||||
toUpdate.size(),
|
toUpdate.size(),
|
||||||
finished,
|
|
||||||
uniqueLeaderboardIds.size()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sleep to prevent rate limiting
|
finished,
|
||||||
try {
|
rankedLeaderboards.size()
|
||||||
Thread.sleep(UPDATE_DELAY);
|
);
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("Failed to sleep for rate limit reset", e);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("An error occurred while updating leaderboard '{}'", id, 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()));
|
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
|
// Update the document
|
||||||
document.put("currentCurveVersion", platform.getCurrentCurveVersion());
|
document.put("currentCurveVersion", platform.getCurrentCurveVersion());
|
||||||
MongoService.INSTANCE.getPlatformsCollection().replaceOne(Filters.eq("_id", platform.getPlatform().getPlatformName()), document);
|
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.common.Request;
|
||||||
import cc.fascinated.exception.impl.BadRequestException;
|
import cc.fascinated.exception.impl.BadRequestException;
|
||||||
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
import cc.fascinated.model.token.ScoreSaberAccountToken;
|
||||||
|
import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken;
|
||||||
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
|
import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
|
||||||
import cc.fascinated.model.user.User;
|
import cc.fascinated.model.user.User;
|
||||||
import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository;
|
import cc.fascinated.repository.mongo.ScoreSaberLeaderboardRepository;
|
||||||
import kong.unirest.core.HttpResponse;
|
import kong.unirest.core.HttpResponse;
|
||||||
import lombok.NonNull;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Fascinated (fascinated7)
|
* @author Fascinated (fascinated7)
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Log4j2(topic = "ScoreSaber Service")
|
||||||
public class ScoreSaberService {
|
public class ScoreSaberService {
|
||||||
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_LEADERBOARD_ENDPOINT = SCORESABER_API + "leaderboard/by-id/%s/info";
|
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.
|
* The ScoreSaber leaderboard repository to use.
|
||||||
@ -80,6 +88,47 @@ public class ScoreSaberService {
|
|||||||
return leaderboard;
|
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.
|
* 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.ScoreSaberScoreToken;
|
||||||
import cc.fascinated.model.token.ScoreSaberWebsocketDataToken;
|
import cc.fascinated.model.token.ScoreSaberWebsocketDataToken;
|
||||||
import cc.fascinated.platform.Platform;
|
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.QuestDBService;
|
||||||
import cc.fascinated.services.UserService;
|
import cc.fascinated.services.UserService;
|
||||||
import cc.fascinated.websocket.Websocket;
|
import cc.fascinated.websocket.Websocket;
|
||||||
@ -39,12 +41,19 @@ public class ScoreSaberWebsocket extends Websocket {
|
|||||||
*/
|
*/
|
||||||
private final QuestDBService questDBService;
|
private final QuestDBService questDBService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The platform service to use
|
||||||
|
*/
|
||||||
|
private final PlatformService platformService;
|
||||||
|
|
||||||
@Autowired
|
@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");
|
super("ScoreSaber", "wss://scoresaber.com/ws");
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.questDBService = questDBService;
|
this.questDBService = questDBService;
|
||||||
|
this.platformService = platformService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override @SneakyThrows
|
@Override @SneakyThrows
|
||||||
@ -71,6 +80,7 @@ public class ScoreSaberWebsocket extends Websocket {
|
|||||||
|
|
||||||
double accuracy = ((double) score.getBaseScore() / leaderboard.getMaxScore()) * 100;
|
double accuracy = ((double) score.getBaseScore() / leaderboard.getMaxScore()) * 100;
|
||||||
String difficulty = ScoreSaberUtils.parseDifficulty(leaderboard.getDifficulty().getDifficulty());
|
String difficulty = ScoreSaberUtils.parseDifficulty(leaderboard.getDifficulty().getDifficulty());
|
||||||
|
double pp = platformService.getScoreSaberPlatform().getPp(leaderboard.getStars(), accuracy); // Recalculate the PP
|
||||||
|
|
||||||
try (Sender sender = questDBService.getSender()) {
|
try (Sender sender = questDBService.getSender()) {
|
||||||
sender.table("score")
|
sender.table("score")
|
||||||
@ -80,7 +90,7 @@ public class ScoreSaberWebsocket extends Websocket {
|
|||||||
// Score information
|
// Score information
|
||||||
.symbol("leaderboard_id", leaderboard.getId())
|
.symbol("leaderboard_id", leaderboard.getId())
|
||||||
.symbol("score_id", score.getId())
|
.symbol("score_id", score.getId())
|
||||||
.doubleColumn("pp", score.getPp())
|
.doubleColumn("pp", pp)
|
||||||
.longColumn("rank", score.getRank())
|
.longColumn("rank", score.getRank())
|
||||||
.longColumn("score", score.getBaseScore())
|
.longColumn("score", score.getBaseScore())
|
||||||
.longColumn("modified_score", score.getModifiedScore())
|
.longColumn("modified_score", score.getModifiedScore())
|
||||||
|
Reference in New Issue
Block a user