Fix scores from friends

This commit is contained in:
pleb
2026-04-11 17:59:45 -07:00
parent d73c1ac495
commit f2181c7cf3
7 changed files with 91 additions and 16 deletions
+9 -2
View File
@@ -9,6 +9,7 @@ import type {
const BASE_URL = "https://api.beatleader.com";
const PAGE_SIZE = 100;
const USE_RUNTIME_PROXY = typeof document !== "undefined";
export type FriendMode = "mutual" | "following" | "followers";
function beatleaderUrl(path: string): string {
if (USE_RUNTIME_PROXY) {
@@ -162,15 +163,21 @@ async function fetchAllFollowers(
}
export async function fetchMutualFriendIds(playerId: string, maxPages = 100): Promise<Set<string>> {
return fetchFriendIds(playerId, "mutual", maxPages);
}
export async function fetchFriendIds(playerId: string, mode: FriendMode, maxPages = 100): Promise<Set<string>> {
const canonicalPlayerId = await resolveBeatLeaderPlayerId(playerId);
const [following, followers] = await Promise.all([
fetchAllFollowers(canonicalPlayerId, "Following", maxPages),
fetchAllFollowers(canonicalPlayerId, "Followers", maxPages),
]);
const followingIds = new Set(following.map((entry) => String(entry.id)));
const followerIds = new Set(followers.map((entry) => String(entry.id)));
if (mode === "following") return followingIds;
if (mode === "followers") return followerIds;
const mutuals = new Set<string>();
for (const entry of followers) {
const id = String(entry.id);
for (const id of followerIds) {
if (followingIds.has(id)) {
mutuals.add(id);
}
+35 -6
View File
@@ -9,7 +9,8 @@ import { fetchBeatSaverMapById, fetchBeatSaverMeta } from "./beatsaver.ts";
import {
fetchAllMapScoresByHash,
fetchBLLeaderboardsByHash,
fetchMutualFriendIds,
fetchFriendIds,
type FriendMode,
normalizeAccuracy,
} from "./beatleader.ts";
@@ -176,6 +177,8 @@ const mistakes = must<HTMLElement>("mistakes");
const friendScoresPanel = must<HTMLElement>("friendScores");
const friendScoresList = must<HTMLOListElement>("friendScoresList");
const friendScoresEmpty = must<HTMLElement>("friendScoresEmpty");
const friendScoresHeaderText = must<HTMLElement>("friendScoresHeaderText");
const friendScoresHeaderImg = must<HTMLImageElement>("friendScoresHeaderImg");
let currentMapHash = "";
let friendScoreRequestId = 0;
@@ -190,6 +193,8 @@ function updateScore(score: Score) {
function clearFriendScores(message: string) {
friendScoresList.replaceChildren();
friendScoresEmpty.textContent = message;
friendScoresHeaderText.textContent = "frenz?";
friendScoresHeaderImg.src = "assets/notlikesteve.webp";
friendScoresPanel.classList.remove("has-items", "is-loading");
}
@@ -197,7 +202,9 @@ function renderFriendScores(items: Array<{ name: string; acc: number }>) {
friendScoresList.replaceChildren();
friendScoresPanel.classList.toggle("has-items", items.length > 0);
friendScoresPanel.classList.remove("is-loading");
friendScoresEmpty.textContent = items.length ? "" : "No mutual scores on this map";
friendScoresEmpty.textContent = items.length ? "" : "No friend scores on this map";
friendScoresHeaderText.textContent = items.length ? "frenz!" : "frenz?";
friendScoresHeaderImg.src = items.length ? "assets/peepohigh.webp" : "assets/notlikesteve.webp";
for (const item of items) {
const li = document.createElement("li");
li.className = "friend-score-item";
@@ -233,7 +240,7 @@ async function refreshMapFriendScores() {
try {
const [leaderboards, mutualFriendIds] = await Promise.all([
fetchBLLeaderboardsByHash(hash),
fetchMutualFriendIds(playerId),
fetchFriendIds(playerId, settings.friendMode),
]);
if (requestId !== friendScoreRequestId) return;
if (leaderboards.length === 0) {
@@ -241,7 +248,12 @@ async function refreshMapFriendScores() {
return;
}
if (mutualFriendIds.size === 0) {
clearFriendScores("No mutual BeatLeader followers");
const relationLabel = settings.friendMode === "following"
? "No followed BeatLeader players"
: settings.friendMode === "followers"
? "No BeatLeader followers"
: "No mutual BeatLeader followers";
clearFriendScores(relationLabel);
return;
}
const scores = await fetchAllMapScoresByHash(hash, leaderboards);
@@ -280,6 +292,7 @@ interface Settings {
time: boolean;
score: boolean;
friends: boolean;
friendMode: FriendMode;
bsr: boolean;
debug: boolean;
mockBsr: string;
@@ -296,6 +309,7 @@ const settings: Settings = {
time: true,
score: true,
friends: true,
friendMode: "mutual",
bsr: false,
debug: false,
mockBsr: "4f4e4",
@@ -380,6 +394,14 @@ for (const key of ["cover", "mapInfo", "time", "score", "friends", "bsr", "debug
};
}
const friendModeInput = must<HTMLSelectElement>("friendModeInput");
friendModeInput.value = settings.friendMode;
friendModeInput.onchange = () => {
settings.friendMode = friendModeInput.value as FriendMode;
saveSettings();
void refreshMapFriendScores();
};
const mockBsrInput = must<HTMLInputElement>("mockBsrInput");
mockBsrInput.value = settings.mockBsr;
mockBsrInput.oninput = () => {
@@ -430,6 +452,7 @@ const requestListEl = must<HTMLOListElement>("requestList");
const requestOverlayEl = must<HTMLElement>("requestOverlay");
const requestEmptyEl = must<HTMLElement>("requestEmpty");
const requestTitleCache = new Map<string, string>();
const requestTitleMisses = new Set<string>();
function useRequestHistorySim() {
return settings.debug || new URLSearchParams(location.search).get("debug") === "1";
@@ -473,20 +496,26 @@ function requesterLine(item: ChatRequestEntry) {
}
async function enrichRequestTitle(key: string, titleEl: HTMLElement) {
if (requestTitleMisses.has(key)) return;
if (requestTitleCache.has(key)) {
titleEl.textContent = requestTitleCache.get(key) ?? "";
return;
}
try {
const map = await fetchBeatSaverMapById(key);
if (!map) return;
if (!map) {
requestTitleMisses.add(key);
return;
}
const name = map.metadata?.songName ?? map.name;
if (name && typeof name === "string") {
requestTitleCache.set(key, name);
titleEl.textContent = name;
return;
}
requestTitleMisses.add(key);
} catch {
// keep !bsr placeholder
requestTitleMisses.add(key);
}
}