Fix leaderboard fetch logic
This commit is contained in:
parent
d971936445
commit
80539af66e
50
index.js
50
index.js
@ -54,6 +54,22 @@ function beatleaderUrl(path) {
|
||||
}
|
||||
return `${BASE_URL2}${path}`;
|
||||
}
|
||||
function normalizeBeatLeaderDifficultyName(value) {
|
||||
return (value ?? "").toLowerCase().replace(/\s+/g, "").replace("expert+", "expertplus");
|
||||
}
|
||||
function normalizeBeatLeaderModeName(value) {
|
||||
return (value ?? "").toLowerCase().replace(/\s+/g, "");
|
||||
}
|
||||
function leaderboardsMatchingPlayMode(leaderboards, characteristic2, difficultyRaw) {
|
||||
const modeNeedle = normalizeBeatLeaderModeName(characteristic2);
|
||||
const diffNeedle = normalizeBeatLeaderDifficultyName(difficultyRaw);
|
||||
if (!modeNeedle || !diffNeedle) return [];
|
||||
return leaderboards.filter((lb) => {
|
||||
const mode = normalizeBeatLeaderModeName(lb.difficulty?.modeName);
|
||||
const diff = normalizeBeatLeaderDifficultyName(lb.difficulty?.difficultyName);
|
||||
return mode === modeNeedle && diff === diffNeedle;
|
||||
});
|
||||
}
|
||||
async function fetchBLLeaderboardsByHash(hash) {
|
||||
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
||||
try {
|
||||
@ -273,6 +289,8 @@ var friendsRelationCache = null;
|
||||
var friendScoreRequestId = 0;
|
||||
var mapInfoRequestId = 0;
|
||||
var rawLevelHash = "";
|
||||
var currentPlayCharacteristic = "";
|
||||
var currentPlayDifficulty = "";
|
||||
function resolvedHashFromBeatSaverMap(map, fallback) {
|
||||
const v = map.versions?.[0]?.hash;
|
||||
if (typeof v === "string" && v.length > 0) return v.toLowerCase().trim();
|
||||
@ -291,6 +309,7 @@ async function applyDebugSong() {
|
||||
const raw = settings.debugSongId.trim();
|
||||
if (!raw) return;
|
||||
const reqId = ++mapInfoRequestId;
|
||||
beginFriendScoresForNewMapContext();
|
||||
document.body.classList.add("loading");
|
||||
try {
|
||||
const map = await fetchBeatSaverMapForDebug(raw);
|
||||
@ -314,6 +333,8 @@ async function applyDebugSong() {
|
||||
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
||||
rawLevelHash = resolved || fallbackHash;
|
||||
currentMapHash = resolved || fallbackHash;
|
||||
currentPlayCharacteristic = "Standard";
|
||||
currentPlayDifficulty = "ExpertPlus";
|
||||
const v0 = map.versions?.[0];
|
||||
const coverUrl = v0?.coverURL?.trim();
|
||||
cover.src = coverUrl || "images/unknown.svg";
|
||||
@ -370,6 +391,8 @@ async function updateMapInfo(data) {
|
||||
void applyDebugSong();
|
||||
return;
|
||||
}
|
||||
currentPlayCharacteristic = data.characteristic;
|
||||
currentPlayDifficulty = data.difficulty;
|
||||
const reqId = ++mapInfoRequestId;
|
||||
const custom = data.level_id.startsWith("custom_level_");
|
||||
const wip = custom && data.level_id.endsWith("WIP");
|
||||
@ -387,6 +410,7 @@ async function updateMapInfo(data) {
|
||||
bsrKey.textContent = custom && !wip ? "\u2026" : custom ? rawLevelHash || "???" : "???";
|
||||
timeMultiplier = data.timeMultiplier || 1;
|
||||
duration = data.duration / 1e3;
|
||||
beginFriendScoresForNewMapContext();
|
||||
if (custom && !wip) {
|
||||
document.body.classList.add("loading");
|
||||
try {
|
||||
@ -521,6 +545,23 @@ function renderFriendScores(items) {
|
||||
function friendsRelationListKey(playerId) {
|
||||
return `${playerId}\0${settings.friendMode}`;
|
||||
}
|
||||
function beginFriendScoresForNewMapContext() {
|
||||
friendScoreRequestId += 1;
|
||||
if (!settings.friends) return;
|
||||
if (!currentMapHash) {
|
||||
clearFriendScores("No map loaded");
|
||||
return;
|
||||
}
|
||||
const playerId = getEffectivePlayerId();
|
||||
if (!playerId) {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||
}
|
||||
async function refreshMapFriendScores() {
|
||||
const hash = currentMapHash;
|
||||
if (!settings.friends) {
|
||||
@ -536,6 +577,8 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||
const requestId = ++friendScoreRequestId;
|
||||
@ -559,6 +602,11 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("No BeatLeader leaderboards found");
|
||||
return;
|
||||
}
|
||||
const forPlayMode = leaderboardsMatchingPlayMode(leaderboards, currentPlayCharacteristic, currentPlayDifficulty);
|
||||
if (forPlayMode.length === 0) {
|
||||
clearFriendScores("No BeatLeader leaderboard for this difficulty");
|
||||
return;
|
||||
}
|
||||
const friendById = new Map(friends.map((f) => [
|
||||
f.id,
|
||||
f
|
||||
@ -569,7 +617,7 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores(relationLabel);
|
||||
return;
|
||||
}
|
||||
const scores = await fetchAllMapScoresByHash(hash, leaderboards);
|
||||
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
const bestByPlayer = /* @__PURE__ */ new Map();
|
||||
for (const score of scores) {
|
||||
|
||||
@ -28,6 +28,31 @@ function beatleaderUrl(path: string): string {
|
||||
return `${BASE_URL}${path}`;
|
||||
}
|
||||
|
||||
/** Match BS+ / BeatSaver difficulty strings to BeatLeader `difficultyName` (handles Expert+ vs ExpertPlus). */
|
||||
export function normalizeBeatLeaderDifficultyName(value: string | null | undefined): string {
|
||||
return (value ?? "").toLowerCase().replace(/\s+/g, "").replace("expert+", "expertplus");
|
||||
}
|
||||
|
||||
function normalizeBeatLeaderModeName(value: string | null | undefined): string {
|
||||
return (value ?? "").toLowerCase().replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
/** Keep only the leaderboard row for the played characteristic + difficulty (hash can list every diff). */
|
||||
export function leaderboardsMatchingPlayMode(
|
||||
leaderboards: BeatLeaderLeaderboard[],
|
||||
characteristic: string,
|
||||
difficultyRaw: string,
|
||||
): BeatLeaderLeaderboard[] {
|
||||
const modeNeedle = normalizeBeatLeaderModeName(characteristic);
|
||||
const diffNeedle = normalizeBeatLeaderDifficultyName(difficultyRaw);
|
||||
if (!modeNeedle || !diffNeedle) return [];
|
||||
return leaderboards.filter((lb) => {
|
||||
const mode = normalizeBeatLeaderModeName(lb.difficulty?.modeName);
|
||||
const diff = normalizeBeatLeaderDifficultyName(lb.difficulty?.difficultyName);
|
||||
return mode === modeNeedle && diff === diffNeedle;
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchBLLeaderboardsByHash(hash: string): Promise<BeatLeaderLeaderboard[]> {
|
||||
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
||||
try {
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
fetchBeatLeaderPlayer,
|
||||
fetchBLLeaderboardsByHash,
|
||||
fetchFriends,
|
||||
leaderboardsMatchingPlayMode,
|
||||
normalizeAccuracy,
|
||||
} from "./beatleader.ts";
|
||||
import { mergeOverlayConfigResponse, type OverlayConfigApiBody } from "./overlay-config.ts";
|
||||
@ -120,6 +121,9 @@ let friendScoreRequestId = 0;
|
||||
let mapInfoRequestId = 0;
|
||||
/** Hex hash from BS+ `level_id` (before BeatSaver version hash). */
|
||||
let rawLevelHash = "";
|
||||
/** BeatLeader friend scores are limited to this characteristic + difficulty (from BS+ mapInfo, or debug BSR defaults). */
|
||||
let currentPlayCharacteristic = "";
|
||||
let currentPlayDifficulty = "";
|
||||
|
||||
function beatLeaderboardId(lb: BeatLeaderLeaderboard): string {
|
||||
const id = lb.id ?? lb.leaderboardId;
|
||||
@ -148,6 +152,7 @@ async function applyDebugSong() {
|
||||
const raw = settings.debugSongId.trim();
|
||||
if (!raw) return;
|
||||
const reqId = ++mapInfoRequestId;
|
||||
beginFriendScoresForNewMapContext();
|
||||
document.body.classList.add("loading");
|
||||
try {
|
||||
const map = await fetchBeatSaverMapForDebug(raw);
|
||||
@ -171,6 +176,9 @@ async function applyDebugSong() {
|
||||
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
||||
rawLevelHash = resolved || fallbackHash;
|
||||
currentMapHash = resolved || fallbackHash;
|
||||
// Debug BSR has no BS+ difficulty; assume Standard ExpertPlus for BeatLeader lookup.
|
||||
currentPlayCharacteristic = "Standard";
|
||||
currentPlayDifficulty = "ExpertPlus";
|
||||
|
||||
const v0 = map.versions?.[0];
|
||||
const coverUrl = v0?.coverURL?.trim();
|
||||
@ -237,6 +245,8 @@ async function updateMapInfo(data: MapInfo) {
|
||||
void applyDebugSong();
|
||||
return;
|
||||
}
|
||||
currentPlayCharacteristic = data.characteristic;
|
||||
currentPlayDifficulty = data.difficulty;
|
||||
const reqId = ++mapInfoRequestId;
|
||||
const custom = data.level_id.startsWith("custom_level_");
|
||||
const wip = custom && data.level_id.endsWith("WIP");
|
||||
@ -256,6 +266,8 @@ async function updateMapInfo(data: MapInfo) {
|
||||
timeMultiplier = data.timeMultiplier || 1;
|
||||
duration = data.duration / 1000;
|
||||
|
||||
beginFriendScoresForNewMapContext();
|
||||
|
||||
if (custom && !wip) {
|
||||
document.body.classList.add("loading");
|
||||
try {
|
||||
@ -409,6 +421,28 @@ function friendsRelationListKey(playerId: string): string {
|
||||
return `${playerId}\0${settings.friendMode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call synchronously when map identity / difficulty changes so stale in-flight fetches cannot repaint,
|
||||
* and the panel does not keep showing the previous map’s scores while BeatLeader loads.
|
||||
*/
|
||||
function beginFriendScoresForNewMapContext() {
|
||||
friendScoreRequestId += 1;
|
||||
if (!settings.friends) return;
|
||||
if (!currentMapHash) {
|
||||
clearFriendScores("No map loaded");
|
||||
return;
|
||||
}
|
||||
const playerId = getEffectivePlayerId();
|
||||
if (!playerId) {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||
}
|
||||
|
||||
async function refreshMapFriendScores() {
|
||||
const hash = currentMapHash;
|
||||
if (!settings.friends) {
|
||||
@ -424,6 +458,8 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("Waiting for BeatLeader player id");
|
||||
return;
|
||||
}
|
||||
friendScoresList.replaceChildren();
|
||||
friendScoresPanel.classList.remove("has-items");
|
||||
friendScoresPanel.classList.add("is-loading");
|
||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||
const requestId = ++friendScoreRequestId;
|
||||
@ -447,6 +483,11 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores("No BeatLeader leaderboards found");
|
||||
return;
|
||||
}
|
||||
const forPlayMode = leaderboardsMatchingPlayMode(leaderboards, currentPlayCharacteristic, currentPlayDifficulty);
|
||||
if (forPlayMode.length === 0) {
|
||||
clearFriendScores("No BeatLeader leaderboard for this difficulty");
|
||||
return;
|
||||
}
|
||||
const friendById = new Map(friends.map((f) => [f.id, f]));
|
||||
const mutualFriendIds = new Set(friends.map((f) => f.id));
|
||||
if (mutualFriendIds.size === 0) {
|
||||
@ -458,7 +499,7 @@ async function refreshMapFriendScores() {
|
||||
clearFriendScores(relationLabel);
|
||||
return;
|
||||
}
|
||||
const scores = await fetchAllMapScoresByHash(hash, leaderboards);
|
||||
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||
if (requestId !== friendScoreRequestId) return;
|
||||
const bestByPlayer = new Map<string, { name: string; acc: number; avatar: string | null }>();
|
||||
for (const score of scores) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user