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}`;
|
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) {
|
async function fetchBLLeaderboardsByHash(hash) {
|
||||||
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
||||||
try {
|
try {
|
||||||
@ -273,6 +289,8 @@ var friendsRelationCache = null;
|
|||||||
var friendScoreRequestId = 0;
|
var friendScoreRequestId = 0;
|
||||||
var mapInfoRequestId = 0;
|
var mapInfoRequestId = 0;
|
||||||
var rawLevelHash = "";
|
var rawLevelHash = "";
|
||||||
|
var currentPlayCharacteristic = "";
|
||||||
|
var currentPlayDifficulty = "";
|
||||||
function resolvedHashFromBeatSaverMap(map, fallback) {
|
function resolvedHashFromBeatSaverMap(map, fallback) {
|
||||||
const v = map.versions?.[0]?.hash;
|
const v = map.versions?.[0]?.hash;
|
||||||
if (typeof v === "string" && v.length > 0) return v.toLowerCase().trim();
|
if (typeof v === "string" && v.length > 0) return v.toLowerCase().trim();
|
||||||
@ -291,6 +309,7 @@ async function applyDebugSong() {
|
|||||||
const raw = settings.debugSongId.trim();
|
const raw = settings.debugSongId.trim();
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
const reqId = ++mapInfoRequestId;
|
const reqId = ++mapInfoRequestId;
|
||||||
|
beginFriendScoresForNewMapContext();
|
||||||
document.body.classList.add("loading");
|
document.body.classList.add("loading");
|
||||||
try {
|
try {
|
||||||
const map = await fetchBeatSaverMapForDebug(raw);
|
const map = await fetchBeatSaverMapForDebug(raw);
|
||||||
@ -314,6 +333,8 @@ async function applyDebugSong() {
|
|||||||
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
||||||
rawLevelHash = resolved || fallbackHash;
|
rawLevelHash = resolved || fallbackHash;
|
||||||
currentMapHash = resolved || fallbackHash;
|
currentMapHash = resolved || fallbackHash;
|
||||||
|
currentPlayCharacteristic = "Standard";
|
||||||
|
currentPlayDifficulty = "ExpertPlus";
|
||||||
const v0 = map.versions?.[0];
|
const v0 = map.versions?.[0];
|
||||||
const coverUrl = v0?.coverURL?.trim();
|
const coverUrl = v0?.coverURL?.trim();
|
||||||
cover.src = coverUrl || "images/unknown.svg";
|
cover.src = coverUrl || "images/unknown.svg";
|
||||||
@ -370,6 +391,8 @@ async function updateMapInfo(data) {
|
|||||||
void applyDebugSong();
|
void applyDebugSong();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
currentPlayCharacteristic = data.characteristic;
|
||||||
|
currentPlayDifficulty = data.difficulty;
|
||||||
const reqId = ++mapInfoRequestId;
|
const reqId = ++mapInfoRequestId;
|
||||||
const custom = data.level_id.startsWith("custom_level_");
|
const custom = data.level_id.startsWith("custom_level_");
|
||||||
const wip = custom && data.level_id.endsWith("WIP");
|
const wip = custom && data.level_id.endsWith("WIP");
|
||||||
@ -387,6 +410,7 @@ async function updateMapInfo(data) {
|
|||||||
bsrKey.textContent = custom && !wip ? "\u2026" : custom ? rawLevelHash || "???" : "???";
|
bsrKey.textContent = custom && !wip ? "\u2026" : custom ? rawLevelHash || "???" : "???";
|
||||||
timeMultiplier = data.timeMultiplier || 1;
|
timeMultiplier = data.timeMultiplier || 1;
|
||||||
duration = data.duration / 1e3;
|
duration = data.duration / 1e3;
|
||||||
|
beginFriendScoresForNewMapContext();
|
||||||
if (custom && !wip) {
|
if (custom && !wip) {
|
||||||
document.body.classList.add("loading");
|
document.body.classList.add("loading");
|
||||||
try {
|
try {
|
||||||
@ -521,6 +545,23 @@ function renderFriendScores(items) {
|
|||||||
function friendsRelationListKey(playerId) {
|
function friendsRelationListKey(playerId) {
|
||||||
return `${playerId}\0${settings.friendMode}`;
|
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() {
|
async function refreshMapFriendScores() {
|
||||||
const hash = currentMapHash;
|
const hash = currentMapHash;
|
||||||
if (!settings.friends) {
|
if (!settings.friends) {
|
||||||
@ -536,6 +577,8 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores("Waiting for BeatLeader player id");
|
clearFriendScores("Waiting for BeatLeader player id");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
friendScoresList.replaceChildren();
|
||||||
|
friendScoresPanel.classList.remove("has-items");
|
||||||
friendScoresPanel.classList.add("is-loading");
|
friendScoresPanel.classList.add("is-loading");
|
||||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||||
const requestId = ++friendScoreRequestId;
|
const requestId = ++friendScoreRequestId;
|
||||||
@ -559,6 +602,11 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores("No BeatLeader leaderboards found");
|
clearFriendScores("No BeatLeader leaderboards found");
|
||||||
return;
|
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) => [
|
const friendById = new Map(friends.map((f) => [
|
||||||
f.id,
|
f.id,
|
||||||
f
|
f
|
||||||
@ -569,7 +617,7 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores(relationLabel);
|
clearFriendScores(relationLabel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const scores = await fetchAllMapScoresByHash(hash, leaderboards);
|
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||||
if (requestId !== friendScoreRequestId) return;
|
if (requestId !== friendScoreRequestId) return;
|
||||||
const bestByPlayer = /* @__PURE__ */ new Map();
|
const bestByPlayer = /* @__PURE__ */ new Map();
|
||||||
for (const score of scores) {
|
for (const score of scores) {
|
||||||
|
|||||||
@ -28,6 +28,31 @@ function beatleaderUrl(path: string): string {
|
|||||||
return `${BASE_URL}${path}`;
|
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[]> {
|
export async function fetchBLLeaderboardsByHash(hash: string): Promise<BeatLeaderLeaderboard[]> {
|
||||||
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
const path = `/leaderboards/hash/${encodeURIComponent(hash)}`;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
fetchBeatLeaderPlayer,
|
fetchBeatLeaderPlayer,
|
||||||
fetchBLLeaderboardsByHash,
|
fetchBLLeaderboardsByHash,
|
||||||
fetchFriends,
|
fetchFriends,
|
||||||
|
leaderboardsMatchingPlayMode,
|
||||||
normalizeAccuracy,
|
normalizeAccuracy,
|
||||||
} from "./beatleader.ts";
|
} from "./beatleader.ts";
|
||||||
import { mergeOverlayConfigResponse, type OverlayConfigApiBody } from "./overlay-config.ts";
|
import { mergeOverlayConfigResponse, type OverlayConfigApiBody } from "./overlay-config.ts";
|
||||||
@ -120,6 +121,9 @@ let friendScoreRequestId = 0;
|
|||||||
let mapInfoRequestId = 0;
|
let mapInfoRequestId = 0;
|
||||||
/** Hex hash from BS+ `level_id` (before BeatSaver version hash). */
|
/** Hex hash from BS+ `level_id` (before BeatSaver version hash). */
|
||||||
let rawLevelHash = "";
|
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 {
|
function beatLeaderboardId(lb: BeatLeaderLeaderboard): string {
|
||||||
const id = lb.id ?? lb.leaderboardId;
|
const id = lb.id ?? lb.leaderboardId;
|
||||||
@ -148,6 +152,7 @@ async function applyDebugSong() {
|
|||||||
const raw = settings.debugSongId.trim();
|
const raw = settings.debugSongId.trim();
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
const reqId = ++mapInfoRequestId;
|
const reqId = ++mapInfoRequestId;
|
||||||
|
beginFriendScoresForNewMapContext();
|
||||||
document.body.classList.add("loading");
|
document.body.classList.add("loading");
|
||||||
try {
|
try {
|
||||||
const map = await fetchBeatSaverMapForDebug(raw);
|
const map = await fetchBeatSaverMapForDebug(raw);
|
||||||
@ -171,6 +176,9 @@ async function applyDebugSong() {
|
|||||||
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
const resolved = resolvedHashFromBeatSaverMap(map, fallbackHash);
|
||||||
rawLevelHash = resolved || fallbackHash;
|
rawLevelHash = resolved || fallbackHash;
|
||||||
currentMapHash = 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 v0 = map.versions?.[0];
|
||||||
const coverUrl = v0?.coverURL?.trim();
|
const coverUrl = v0?.coverURL?.trim();
|
||||||
@ -237,6 +245,8 @@ async function updateMapInfo(data: MapInfo) {
|
|||||||
void applyDebugSong();
|
void applyDebugSong();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
currentPlayCharacteristic = data.characteristic;
|
||||||
|
currentPlayDifficulty = data.difficulty;
|
||||||
const reqId = ++mapInfoRequestId;
|
const reqId = ++mapInfoRequestId;
|
||||||
const custom = data.level_id.startsWith("custom_level_");
|
const custom = data.level_id.startsWith("custom_level_");
|
||||||
const wip = custom && data.level_id.endsWith("WIP");
|
const wip = custom && data.level_id.endsWith("WIP");
|
||||||
@ -256,6 +266,8 @@ async function updateMapInfo(data: MapInfo) {
|
|||||||
timeMultiplier = data.timeMultiplier || 1;
|
timeMultiplier = data.timeMultiplier || 1;
|
||||||
duration = data.duration / 1000;
|
duration = data.duration / 1000;
|
||||||
|
|
||||||
|
beginFriendScoresForNewMapContext();
|
||||||
|
|
||||||
if (custom && !wip) {
|
if (custom && !wip) {
|
||||||
document.body.classList.add("loading");
|
document.body.classList.add("loading");
|
||||||
try {
|
try {
|
||||||
@ -409,6 +421,28 @@ function friendsRelationListKey(playerId: string): string {
|
|||||||
return `${playerId}\0${settings.friendMode}`;
|
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() {
|
async function refreshMapFriendScores() {
|
||||||
const hash = currentMapHash;
|
const hash = currentMapHash;
|
||||||
if (!settings.friends) {
|
if (!settings.friends) {
|
||||||
@ -424,6 +458,8 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores("Waiting for BeatLeader player id");
|
clearFriendScores("Waiting for BeatLeader player id");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
friendScoresList.replaceChildren();
|
||||||
|
friendScoresPanel.classList.remove("has-items");
|
||||||
friendScoresPanel.classList.add("is-loading");
|
friendScoresPanel.classList.add("is-loading");
|
||||||
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
friendScoresEmpty.textContent = "Loading mutual friend scores...";
|
||||||
const requestId = ++friendScoreRequestId;
|
const requestId = ++friendScoreRequestId;
|
||||||
@ -447,6 +483,11 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores("No BeatLeader leaderboards found");
|
clearFriendScores("No BeatLeader leaderboards found");
|
||||||
return;
|
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 friendById = new Map(friends.map((f) => [f.id, f]));
|
||||||
const mutualFriendIds = new Set(friends.map((f) => f.id));
|
const mutualFriendIds = new Set(friends.map((f) => f.id));
|
||||||
if (mutualFriendIds.size === 0) {
|
if (mutualFriendIds.size === 0) {
|
||||||
@ -458,7 +499,7 @@ async function refreshMapFriendScores() {
|
|||||||
clearFriendScores(relationLabel);
|
clearFriendScores(relationLabel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const scores = await fetchAllMapScoresByHash(hash, leaderboards);
|
const scores = await fetchAllMapScoresByHash(hash, forPlayMode);
|
||||||
if (requestId !== friendScoreRequestId) return;
|
if (requestId !== friendScoreRequestId) return;
|
||||||
const bestByPlayer = new Map<string, { name: string; acc: number; avatar: string | null }>();
|
const bestByPlayer = new Map<string, { name: string; acc: number; avatar: string | null }>();
|
||||||
for (const score of scores) {
|
for (const score of scores) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user