// src/client/beatsaver.ts var BASE_URL = "https://api.beatsaver.com"; var USE_RUNTIME_PROXY = typeof document !== "undefined"; function beatsaverUrl(path) { if (USE_RUNTIME_PROXY) { return `/api/beatsaver?path=${encodeURIComponent(path)}`; } return `${BASE_URL}${path}`; } async function fetchBeatSaverMeta(hash) { try { const response = await fetch(beatsaverUrl(`/maps/hash/${encodeURIComponent(hash)}`)); if (!response.ok) return null; return await response.json(); } catch { return null; } } async function fetchBeatSaverMapById(mapId) { try { const response = await fetch(beatsaverUrl(`/maps/id/${encodeURIComponent(mapId)}`)); if (!response.ok) return null; return await response.json(); } catch { return null; } } // src/client/overlay-server-log.ts function mirrorOverlayLog(scope, phase, detail) { if (typeof location === "undefined") return; if (location.protocol !== "http:" && location.protocol !== "https:") return; const body = JSON.stringify({ scope, phase, detail }); void fetch("/api/overlay-log", { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, body, keepalive: true }).catch(() => { }); } // src/client/beatleader.ts var BASE_URL2 = "https://api.beatleader.com"; var PAGE_SIZE = 100; var MAX_LEADERBOARD_SCORE_PAGES = 2e3; var USE_RUNTIME_PROXY2 = typeof document !== "undefined"; function blDiag(phase, detail) { if (!USE_RUNTIME_PROXY2) return; console.log(`[BS+ overlay] beatleader:${phase}`, detail); mirrorOverlayLog("beatleader", phase, detail); } function beatleaderUrl(path) { if (USE_RUNTIME_PROXY2) { return `/api/beatleader?path=${encodeURIComponent(path)}`; } return `${BASE_URL2}${path}`; } async function fetchBLLeaderboardsByHash(hash) { const path = `/leaderboards/hash/${encodeURIComponent(hash)}`; blDiag("leaderboardsByHash", { path, hash }); try { const res = await fetch(beatleaderUrl(path)); if (!res.ok) { blDiag("leaderboardsByHash", { path, hash, reason: "http-not-ok", status: res.status, statusText: res.statusText }); return []; } const data = await res.json(); const leaderboards = Array.isArray(data) ? data : Array.isArray(data.leaderboards) ? data.leaderboards : []; blDiag("leaderboardsByHash", { path, hash, count: leaderboards.length }); return leaderboards; } catch (err) { blDiag("leaderboardsByHash", { path, hash, reason: "fetch-error", error: String(err) }); return []; } } async function resolveBeatLeaderPlayerId(playerId) { const path = `/player/${encodeURIComponent(playerId)}`; blDiag("resolvePlayer", { path, playerId }); try { const res = await fetch(beatleaderUrl(path)); if (!res.ok) { blDiag("resolvePlayer", { path, playerId, reason: "http-not-ok", status: res.status, usingId: playerId }); return playerId; } const data = await res.json(); const canonicalId = data.id; const out = canonicalId == null ? playerId : String(canonicalId); blDiag("resolvePlayer", { path, playerId, canonicalId: out, changed: out !== playerId }); return out; } catch (err) { blDiag("resolvePlayer", { path, playerId, reason: "fetch-error", error: String(err), usingId: playerId }); return playerId; } } async function fetchLeaderboardScoresById(leaderboardId, maxPages = MAX_LEADERBOARD_SCORE_PAGES) { const scores = []; const pageSize = PAGE_SIZE; let page = 1; let hitPageCap = false; for (; ; ) { if (page > maxPages) { hitPageCap = true; break; } const qs = new URLSearchParams({ leaderboardContext: "general", page: String(page), sortBy: "rank", order: "desc", count: String(pageSize) }); const path = `/leaderboard/${encodeURIComponent(leaderboardId)}?${qs}`; const url = beatleaderUrl(path); let res; try { res = await fetch(url); } catch (err) { blDiag("leaderboardScores", { leaderboardId, page, path, reason: "fetch-error", error: String(err) }); break; } if (!res.ok) { blDiag("leaderboardScores", { leaderboardId, page, path, reason: "http-not-ok", status: res.status, statusText: res.statusText }); break; } const payload = await res.json(); const batch = Array.isArray(payload.scores) ? payload.scores : []; if (page === 1) { blDiag("leaderboardScores", { leaderboardId, page, path, firstPageBatchSize: batch.length, pageSize }); } if (batch.length === 0) break; scores.push(...batch); if (batch.length < pageSize) break; page += 1; } blDiag("leaderboardScoresTotal", { leaderboardId, totalScores: scores.length, ...hitPageCap ? { warning: "hit-max-pages-cap", maxPages } : {} }); return scores; } async function fetchAllMapScoresByHash(hash, leaderboards, maxPagesPerLeaderboard = MAX_LEADERBOARD_SCORE_PAGES) { const ids = leaderboards.map((lb) => lb.id == null ? "" : String(lb.id)).filter(Boolean); blDiag("fetchAllMapScoresByHash", { hash, leaderboardCount: leaderboards.length, leaderboardIds: ids }); const requests = leaderboards.map((lb) => { const leaderboardId = lb.id == null ? null : String(lb.id); if (!leaderboardId) return Promise.resolve([]); return fetchLeaderboardScoresById(leaderboardId, maxPagesPerLeaderboard); }); const batches = await Promise.all(requests); const flat = batches.flat(); blDiag("fetchAllMapScoresByHash", { hash, totalScores: flat.length }); return flat; } async function fetchFollowersPage(playerId, type2, page, count) { const qs = new URLSearchParams({ type: type2, page: String(page), count: String(count) }); const path = `/player/${encodeURIComponent(playerId)}/followers?${qs}`; const url = beatleaderUrl(path); try { const response = await fetch(url); if (!response.ok) { blDiag("followersPage", { playerId, type: type2, page, path, reason: "http-not-ok", status: response.status, statusText: response.statusText }); return []; } const data = await response.json(); const rows = Array.isArray(data) ? data : []; if (page === 1) { blDiag("followersPage", { playerId, type: type2, page, path, count: rows.length }); } return rows; } catch (err) { blDiag("followersPage", { playerId, type: type2, page, path, reason: "fetch-error", error: String(err) }); return []; } } async function fetchAllFollowers(playerId, type2, maxPages = 100) { const all = []; for (let page = 1; page <= maxPages; page += 1) { const batch = await fetchFollowersPage(playerId, type2, page, PAGE_SIZE); if (batch.length === 0) break; all.push(...batch); if (batch.length < PAGE_SIZE) break; } return all; } function normalizeFollowerEntry(entry) { return { ...entry, id: String(entry.id) }; } async function fetchFriends(playerId, mode, maxPages = 100) { const canonicalPlayerId = await resolveBeatLeaderPlayerId(playerId); blDiag("fetchFriendsStart", { playerId, canonicalPlayerId, mode, maxPages }); const [following, followers] = await Promise.all([ fetchAllFollowers(canonicalPlayerId, "Following", maxPages), fetchAllFollowers(canonicalPlayerId, "Followers", maxPages) ]); blDiag("fetchFriendsLists", { canonicalPlayerId, mode, followingCount: following.length, followersCount: followers.length }); const followingIds = new Set(following.map((entry) => String(entry.id))); if (mode === "following") { const out2 = following.map((entry) => normalizeFollowerEntry(entry)); blDiag("fetchFriendsResult", { mode, count: out2.length }); return out2; } if (mode === "followers") { const out2 = followers.map((entry) => normalizeFollowerEntry(entry)); blDiag("fetchFriendsResult", { mode, count: out2.length }); return out2; } const out = followers.filter((entry) => followingIds.has(String(entry.id))).map((entry) => normalizeFollowerEntry(entry)); blDiag("fetchFriendsResult", { mode: "mutual", count: out.length, reasonIfEmpty: out.length === 0 ? following.length === 0 || followers.length === 0 ? "missing-following-or-followers-list" : "no-intersection" : void 0 }); return out; } function normalizeAccuracy(value) { if (typeof value !== "number" || !Number.isFinite(value)) return null; return value <= 1 ? value * 100 : value; } // src/client/index.ts function must(id) { const element = document.getElementById(id); if (!element) throw new Error(`Missing element: ${id}`); return element; } function parseJson(raw) { return JSON.parse(raw); } var settings = { cover: true, mapInfo: true, time: true, score: true, friends: true, friendMode: "mutual", bsr: false, debug: false, mockBsr: "4f4e4", debugPlayerId: "76561199407393962", right: false, bottom: true, scale: 1, fade: 300 }; var defaults = structuredClone(settings); var style = document.createElement("style"); function loadSettings() { const params = new URLSearchParams(location.hash.slice(1)); let css = ""; for (const [key, def] of Object.entries(defaults)) { const value = parseJson(params.get(key) || "null") ?? def; settings[key] = value; if (typeof def === "boolean") document.body.classList.toggle(key, Boolean(value)); else css += `--${key}: ${value}; `; } style.textContent = `:root { ${css}}`; } function saveSettings() { const params = new URLSearchParams(); for (const [key, value] of Object.entries(settings)) { if (value !== defaults[key]) params.set(key, JSON.stringify(value)); } location.replace(`#${params.toString()}`); } var beatSaberPlus = { // https://github.com/hardcpp/BeatSaberPlus/wiki/%5BEN%5D-Song-Overlay url: "ws://localhost:2947/socket", onMessage: (e) => { const data = parseJson(e.data); switch (data._type) { case "event": switch (data._event) { case "gameState": document.body.dataset.gameState = data.gameStateChanged; break; case "mapInfo": void updateMapInfo(data.mapInfoChanged); break; case "pause": updateTime(data.pauseTime, true); break; case "resume": updateTime(data.resumeTime, false); break; case "score": updateScore(data.scoreEvent); break; } break; case "handshake": currentPlayerPlatformId = data.playerPlatformId || ""; console.log("[BS+ overlay] BS+ handshake", { playerPlatformId: currentPlayerPlatformId || "(empty)" }); updateDebugHud(); void refreshMapFriendScores(); break; default: console.log("message", e.data); break; } } }; var provider = beatSaberPlus; var retryMs = 1e4; var retries = 0; var currentPlayerPlatformId = ""; function getEffectivePlayerId() { const configured = settings.debugPlayerId.trim(); const raw = configured || currentPlayerPlatformId; if (!raw) return ""; const steamIdCandidate = raw.match(/\d{17,20}/)?.[0]; if (steamIdCandidate) return steamIdCandidate; if (/^\d+$/.test(raw)) return raw; if (configured) return raw; return ""; } var currentMapHash = ""; var friendsRelationCacheKey = ""; var friendsRelationCache = null; var friendScoreRequestId = 0; var mapInfoRequestId = 0; var lastMapLevelId = ""; var lastBsPlusBsrKey = ""; var lastCharDiffStr = ""; var lastBeatSaverIdDisplay = "\u2014"; var lastBeatSaverNote = "\u2014"; var lastFriendScoresDebug = "\u2014"; var rawLevelHash = ""; var lastBeatLeaderLeaderboardIds = "\u2014"; function formatErr(err) { return err instanceof Error ? err.message : String(err); } var debugHud = { levelId: must("debugHudLevelId"), rawHash: must("debugHudRawHash"), hash: must("debugHudHash"), bsPlusBsr: must("debugHudBsPlusBsr"), beatSaverId: must("debugHudBeatSaverId"), beatSaverNote: must("debugHudBeatSaverNote"), charDiff: must("debugHudCharDiff"), handshake: must("debugHudHandshake"), blId: must("debugHudBlId"), blLeaderboards: must("debugHudBlLeaderboards"), friends: must("debugHudFriends") }; function updateDebugHud() { if (!settings.debug) return; debugHud.levelId.textContent = lastMapLevelId || "\u2014"; debugHud.rawHash.textContent = rawLevelHash || "\u2014"; debugHud.hash.textContent = currentMapHash || "\u2014"; debugHud.bsPlusBsr.textContent = lastBsPlusBsrKey || "\u2014"; debugHud.beatSaverId.textContent = lastBeatSaverIdDisplay; debugHud.beatSaverNote.textContent = lastBeatSaverNote; debugHud.charDiff.textContent = lastCharDiffStr || "\u2014"; debugHud.handshake.textContent = currentPlayerPlatformId || "\u2014"; debugHud.blId.textContent = getEffectivePlayerId() || "\u2014"; debugHud.blLeaderboards.textContent = lastBeatLeaderLeaderboardIds; debugHud.friends.textContent = lastFriendScoresDebug; } function beatLeaderboardId(lb) { const id = lb.id ?? lb.leaderboardId; return id == null ? "" : String(id); } function resolvedHashFromBeatSaverMap(map, fallback) { const v = map.versions?.[0]?.hash; if (typeof v === "string" && v.length > 0) return v.toLowerCase().trim(); return fallback; } function connect() { console.log(`Connecting to ${provider.url} (attempt ${retries++})`); const ws = new WebSocket(provider.url); ws.onopen = onOpen; ws.onmessage = provider.onMessage; ws.onclose = onClose; } function onOpen() { console.log("Connection open."); retries = 0; } function onClose(e) { console.log(`Connection closed. code: ${e.code}, reason: ${e.reason}, clean: ${e.wasClean}`); setTimeout(connect, retryMs); } connect(); var cover = must("coverImg"); var title = must("title"); var subTitle = must("subTitle"); var artist = must("artist"); var mapper = must("mapper"); var difficulty = must("difficulty"); var characteristic = must("characteristicImg"); var difficultyLabel = must("difficultyLabel"); var type = must("type"); var bsrKey = must("bsrKey"); var timeMultiplier = 1; var duration = 0; async function updateMapInfo(data) { const reqId = ++mapInfoRequestId; const custom = data.level_id.startsWith("custom_level_"); const wip = custom && data.level_id.endsWith("WIP"); rawLevelHash = custom ? data.level_id.substring(13, 53).toLowerCase() : ""; currentMapHash = rawLevelHash; lastMapLevelId = data.level_id; lastBsPlusBsrKey = data.BSRKey || ""; lastCharDiffStr = `${data.characteristic} / ${data.difficulty}`; lastBeatLeaderLeaderboardIds = "\u2014"; console.log("[BS+ overlay] map: new song", { level_id: data.level_id, hashLevelId: rawLevelHash || "(none)", custom, wip, bsPlusBsrKey: data.BSRKey || "(empty, not used for APIs)", characteristic: data.characteristic, difficulty: data.difficulty }); if (settings.debug) { console.log("[BS+ overlay] map: detail", { requestId: reqId, name: data.name, artist: data.artist }); } cover.src = data.coverRaw ? `data:image/jpeg;base64,${data.coverRaw}` : "images/unknown.svg"; title.textContent = data.name || ""; subTitle.textContent = data.sub_name || ""; artist.textContent = data.artist || ""; mapper.textContent = data.mapper || ""; difficulty.textContent = data.difficulty?.replace("Plus", " +") || ""; characteristic.src = `images/characteristic/${data.characteristic}.svg`; difficultyLabel.textContent = ""; type.textContent = !custom ? "OST" : wip ? "WIP" : ""; bsrKey.textContent = custom && !wip ? "\u2026" : custom ? rawLevelHash || "???" : "???"; timeMultiplier = data.timeMultiplier || 1; duration = data.duration / 1e3; lastBeatSaverIdDisplay = "\u2014"; lastBeatSaverNote = custom && !wip ? "loading\u2026" : wip ? "WIP (no BeatSaver)" : !custom ? "OST (no hash)" : "\u2014"; updateDebugHud(); if (custom && !wip) { document.body.classList.add("loading"); try { console.log("[BS+ overlay] map: BeatSaver lookup by hash (from level_id)", rawLevelHash); const map = await fetchBeatSaverMeta(rawLevelHash); if (reqId !== mapInfoRequestId) return; if (!map?.id) { lastBeatSaverIdDisplay = "\u2014"; lastBeatSaverNote = "BeatSaver: no map (check hash / proxy)"; currentMapHash = rawLevelHash; console.warn("[BS+ overlay] map: BeatSaver miss \u2014 BeatLeader will use level_id hash", { hashLevelId: rawLevelHash }); } else { lastBeatSaverIdDisplay = map.id; lastBeatSaverNote = "ok"; const resolved = resolvedHashFromBeatSaverMap(map, rawLevelHash); if (resolved !== rawLevelHash) { console.log("[BS+ overlay] map: using BeatSaver version hash for BeatLeader", { hashLevelId: rawLevelHash, hashBeatLeader: resolved, beatSaverId: map.id }); } currentMapHash = resolved; console.log("[BS+ overlay] map: BeatSaver ok", { beatSaverId: map.id, hashBeatLeader: currentMapHash }); bsrKey.textContent = map.id; mapper.textContent = map.metadata?.levelAuthorName || ""; const diff = map.versions?.[0]?.diffs?.find((d) => d.characteristic === data.characteristic && d.difficulty === data.difficulty); if (diff?.label) difficultyLabel.textContent = diff.label; } } catch (err) { if (reqId !== mapInfoRequestId) return; lastBeatSaverIdDisplay = "\u2014"; lastBeatSaverNote = `error: ${formatErr(err)}`; currentMapHash = rawLevelHash; console.error("[BS+ overlay] map: BeatSaver fetch failed \u2014 BeatLeader will use level_id hash", err); } finally { document.body.classList.remove("loading"); if (reqId === mapInfoRequestId) { updateDebugHud(); void refreshMapFriendScores(); } } } else { if (custom && wip) { bsrKey.textContent = rawLevelHash || "???"; } else { bsrKey.textContent = "???"; } difficultyLabel.textContent = ""; updateDebugHud(); void refreshMapFriendScores(); } } var timeText = must("timeText"); var timeBar = must("timeBar"); var intervalMs = 500; var intervalId = 0; var currentTime = 0; function updateTime(time, paused) { if (!settings.time) return; setTime(time); clearInterval(intervalId); if (paused) return; intervalId = window.setInterval(() => setTime(currentTime + intervalMs * timeMultiplier / 1e3), intervalMs); } function setTime(time) { currentTime = time; timeText.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`; timeBar.style.width = `${currentTime / (duration || Infinity) * 100}%`; } function formatTime(t) { t = Math.floor(t); const minutes = Math.floor(t / 60); const seconds = t - minutes * 60; return `${minutes}:${String(seconds).padStart(2, "0")}`; } var accuracy = must("accuracy"); var mistakes = must("mistakes"); var friendScoresPanel = must("friendScores"); var friendScoresList = must("friendScoresList"); var friendScoresEmpty = must("friendScoresEmpty"); var friendScoresHeaderText = must("friendScoresHeaderText"); var friendScoresHeaderImg = must("friendScoresHeaderImg"); function updateScore(score) { if (!settings.score) return; accuracy.textContent = (score.accuracy * 100).toFixed(1); mistakes.textContent = score.missCount ? String(score.missCount) : ""; accuracy.classList.toggle("failed", score.currentHealth === 0); } function avatarFromScore(score) { if (typeof score.player === "object" && score.player?.avatar) { return score.player.avatar; } const url = score.playerAvatar?.trim(); return url || null; } function clearFriendScores(message) { friendScoresList.replaceChildren(); friendScoresEmpty.textContent = message; friendScoresHeaderText.textContent = "frenz?"; friendScoresHeaderImg.src = "assets/notlikesteve.webp"; friendScoresPanel.classList.remove("has-items", "is-loading"); } function renderFriendScores(items) { friendScoresList.replaceChildren(); friendScoresPanel.classList.toggle("has-items", items.length > 0); friendScoresPanel.classList.remove("is-loading"); 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"; const avatar = document.createElement("img"); avatar.className = "friend-avatar"; avatar.alt = ""; avatar.decoding = "async"; avatar.loading = "lazy"; avatar.src = item.avatar?.trim() || "images/unknown.svg"; const name = document.createElement("span"); name.className = "friend-name"; name.textContent = item.name; const acc = document.createElement("span"); acc.className = "friend-acc"; acc.textContent = `${item.acc.toFixed(2)}%`; li.append(acc, avatar, name); friendScoresList.appendChild(li); } } function friendsDiag(message, detail = {}) { if (!settings.friends) return; console.log(`[BS+ overlay] friends:${message}`, detail); mirrorOverlayLog("friends", message, detail); } function friendsRelationListKey(playerId) { return `${playerId}\0${settings.friendMode}`; } async function refreshMapFriendScores() { const hash = currentMapHash; if (!settings.friends) { lastFriendScoresDebug = "off"; lastBeatLeaderLeaderboardIds = "\u2014"; updateDebugHud(); clearFriendScores("Disabled in settings"); return; } if (!hash) { friendsDiag("skip", { reason: "no-map-hash", hint: "Need custom map level_id hash or resolved BeatSaver hash" }); lastFriendScoresDebug = "no hash"; lastBeatLeaderLeaderboardIds = "\u2014"; updateDebugHud(); clearFriendScores("No map loaded"); return; } const playerId = getEffectivePlayerId(); if (!playerId) { friendsDiag("skip", { reason: "no-player-id", hint: "Wait for BS+ handshake (playerPlatformId) or set debug BeatLeader id in settings" }); lastFriendScoresDebug = "no BeatLeader player id"; lastBeatLeaderLeaderboardIds = "\u2014"; updateDebugHud(); clearFriendScores("Waiting for BeatLeader player id"); return; } friendScoresPanel.classList.add("is-loading"); friendScoresEmpty.textContent = "Loading mutual friend scores..."; lastFriendScoresDebug = "loading\u2026"; updateDebugHud(); const requestId = ++friendScoreRequestId; friendsDiag("start", { requestId, hash, playerId, friendMode: settings.friendMode, debugPlayerOverride: Boolean(settings.debug && settings.debugPlayerId.trim()) }); try { const relKey = friendsRelationListKey(playerId); const friendsPromise = (async () => { if (friendsRelationCache !== null && relKey === friendsRelationCacheKey) { friendsDiag("friends-list-cache", { hit: true, friendCount: friendsRelationCache.length }); return friendsRelationCache; } const fetched = await fetchFriends(playerId, settings.friendMode); friendsRelationCacheKey = relKey; friendsRelationCache = fetched; friendsDiag("friends-list-cache", { hit: false, friendCount: fetched.length }); return fetched; })(); const [leaderboards, friends] = await Promise.all([ fetchBLLeaderboardsByHash(hash), friendsPromise ]); if (requestId !== friendScoreRequestId) return; const leaderboardIds = leaderboards.map(beatLeaderboardId).filter(Boolean); lastBeatLeaderLeaderboardIds = leaderboardIds.length ? leaderboardIds.join(", ") : "none"; updateDebugHud(); friendsDiag("parallel-fetch-done", { hash, leaderboardCount: leaderboards.length, leaderboardIds, friendCount: friends.length }); if (leaderboards.length === 0) { friendsDiag("empty-ui", { reason: "no-beatleader-leaderboards-for-hash", hash, hint: "BeatLeader has no leaderboards for this map hash (wrong hash, unranked, or API/proxy error \u2014 see beatleader:leaderboardsByHash logs)" }); lastFriendScoresDebug = "0 leaderboards for hash"; updateDebugHud(); clearFriendScores("No BeatLeader leaderboards found"); return; } const friendById = new Map(friends.map((f) => [ f.id, f ])); const mutualFriendIds = new Set(friends.map((f) => f.id)); if (mutualFriendIds.size === 0) { friendsDiag("empty-ui", { reason: "no-friends-for-mode", friendMode: settings.friendMode, hash, hint: "Following/followers lists empty or mutual mode has no intersection \u2014 see beatleader:fetchFriendsResult" }); lastFriendScoresDebug = `0 friends (${settings.friendMode})`; updateDebugHud(); 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); if (requestId !== friendScoreRequestId) return; friendsDiag("scores-aggregated", { hash, rawScoreRows: scores.length, friendIdsInRelation: mutualFriendIds.size }); const bestByPlayer = /* @__PURE__ */ new Map(); for (const score of scores) { const scorePlayerId = score.playerId ?? (typeof score.player === "object" ? score.player?.id : null); const playerKey = scorePlayerId == null ? "" : String(scorePlayerId); if (!playerKey || !mutualFriendIds.has(playerKey)) continue; const acc = normalizeAccuracy(score.accuracy ?? score.acc); if (acc === null) continue; const existing = bestByPlayer.get(playerKey); if (!existing || acc > existing.acc) { const friendMeta = friendById.get(playerKey); const playerName = score.playerName || (typeof score.player === "object" ? score.player?.name : typeof score.player === "string" ? score.player : null); const fromScore = avatarFromScore(score); const fromFriend = friendMeta?.avatar?.trim() || null; bestByPlayer.set(playerKey, { name: playerName || friendMeta?.name || playerKey, acc, avatar: fromScore ?? fromFriend }); } } const sorted = Array.from(bestByPlayer.values()).sort((a, b) => b.acc - a.acc); if (sorted.length === 0 && scores.length > 0) { friendsDiag("empty-ui", { reason: "no-friend-scores-on-leaderboards", hash, rawScoreRows: scores.length, friendIdsInRelation: mutualFriendIds.size, hint: "Leaderboard scores exist but none match friend ids (playerId on scores vs BeatLeader friend ids)" }); } else if (sorted.length === 0 && scores.length === 0) { friendsDiag("empty-ui", { reason: "no-scores-on-map-leaderboards", hash, leaderboardIds, hint: "No ranked rows returned for these leaderboards \u2014 see beatleader:leaderboardScores logs" }); } else { friendsDiag("done", { rows: sorted.length, hash }); } lastFriendScoresDebug = `${leaderboards.length} LB, ${friends.length} friends, ${scores.length} scores \u2192 ${sorted.length} rows`; updateDebugHud(); renderFriendScores(sorted); } catch (err) { if (requestId !== friendScoreRequestId) return; friendsDiag("error", { message: formatErr(err), hash, playerId }); lastFriendScoresDebug = `error: ${formatErr(err)}`; lastBeatLeaderLeaderboardIds = "\u2014"; updateDebugHud(); clearFriendScores("Failed loading BeatLeader scores"); } } async function applyMockMapFromBsr() { const key = settings.mockBsr.trim(); if (!settings.debug || !key) return; const map = await fetchBeatSaverMapById(key); if (!map) { console.warn("[BS+ overlay] map: mock BSR lookup returned nothing", { key }); return; } const hash = map.versions?.[0]?.hash?.toLowerCase?.() || ""; rawLevelHash = hash; currentMapHash = hash; lastBeatLeaderLeaderboardIds = "\u2014"; title.textContent = map.metadata?.songName || map.name || title.textContent || ""; subTitle.textContent = map.metadata?.songSubName || ""; artist.textContent = map.metadata?.songAuthorName || ""; mapper.textContent = map.metadata?.levelAuthorName || ""; bsrKey.textContent = map.id || key; type.textContent = "MOCK"; const coverUrl = map.versions?.[0]?.coverURL; if (coverUrl) cover.src = coverUrl; lastMapLevelId = `mock:${key}`; lastBsPlusBsrKey = ""; lastCharDiffStr = ""; lastBeatSaverIdDisplay = map.id || "\u2014"; lastBeatSaverNote = "mock BSR"; updateDebugHud(); console.log("[BS+ overlay] map: mock from BSR key", { key, hash: currentMapHash, mapId: map.id }); void refreshMapFriendScores(); } window.onhashchange = loadSettings; loadSettings(); document.head.appendChild(style); updateDebugHud(); for (const key of [ "cover", "mapInfo", "time", "score", "friends", "bsr", "debug" ]) { const input = must(`${key}Input`); input.checked = settings[key]; input.oninput = () => { settings[key] = input.checked; saveSettings(); if (key === "friends") void refreshMapFriendScores(); if (key === "debug") { updateDebugHud(); void loadRequestQueue(); void applyMockMapFromBsr(); void refreshMapFriendScores(); } }; } var friendModeInput = must("friendModeInput"); friendModeInput.value = settings.friendMode; friendModeInput.onchange = () => { settings.friendMode = friendModeInput.value; saveSettings(); void refreshMapFriendScores(); }; var mockBsrInput = must("mockBsrInput"); mockBsrInput.value = settings.mockBsr; mockBsrInput.oninput = () => { settings.mockBsr = mockBsrInput.value.trim(); saveSettings(); void applyMockMapFromBsr(); }; var beatLeaderPlayerInput = must("beatLeaderPlayerInput"); beatLeaderPlayerInput.value = settings.debugPlayerId; beatLeaderPlayerInput.oninput = () => { settings.debugPlayerId = beatLeaderPlayerInput.value.trim(); saveSettings(); void refreshMapFriendScores(); }; void applyMockMapFromBsr(); var scale = must("scaleInput"); scale.valueAsNumber = settings.scale * 100; scale.oninput = () => { settings.scale = scale.valueAsNumber / 100; saveSettings(); }; var position = must("positionInput"); position.value = JSON.stringify([ settings.right, settings.bottom ]); position.onchange = () => { [settings.right, settings.bottom] = parseJson(position.value); saveSettings(); }; var fade = must("fadeInput"); fade.valueAsNumber = settings.fade; fade.oninput = () => { settings.fade = fade.valueAsNumber; saveSettings(); }; document.documentElement.onclick = () => document.body.classList.toggle("preview"); must("settings").onclick = (e) => e.stopPropagation(); var MAX_REQUESTS = 10; var REQUEST_POLL_MS = 5e3; var requestListEl = must("requestList"); var requestOverlayEl = must("requestOverlay"); var requestEmptyEl = must("requestEmpty"); var requestTitleCache = /* @__PURE__ */ new Map(); var requestTitleMisses = /* @__PURE__ */ new Set(); function useRequestHistorySim() { return settings.debug || new URLSearchParams(location.search).get("debug") === "1"; } function requestJsonFilenames() { const explicit = new URLSearchParams(location.search).get("requests"); if (explicit) return [ explicit ]; if (useRequestHistorySim()) return [ "ChatRequest.json", "database.json" ]; return [ "ChatRequest.json" ]; } function loadJsonNextToPage(fileName) { const base = new URL(fileName, location.href); if (base.protocol !== "http:" && base.protocol !== "https:") { throw new Error("not-http"); } const busted = new URL(base.href); busted.searchParams.set("t", String(Date.now())); return fetch(busted.href, { cache: "no-store" }).then((res) => { if (!res.ok) throw new Error(String(res.status)); return res.json(); }); } async function loadRequestPayload() { let lastErr; for (const name of requestJsonFilenames()) { try { return await loadJsonNextToPage(name); } catch (e) { lastErr = e; } } throw lastErr instanceof Error ? lastErr : new Error(String(lastErr)); } function requesterLine(item) { const parts = [ item.npr, item.rqn ].filter(Boolean); return parts.length ? parts.join(" ") : item.rqn || ""; } async function enrichRequestTitle(key, titleEl) { if (requestTitleMisses.has(key)) return; if (requestTitleCache.has(key)) { titleEl.textContent = requestTitleCache.get(key) ?? ""; return; } try { const map = await fetchBeatSaverMapById(key); 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 { requestTitleMisses.add(key); } } function renderRequestList(items) { requestListEl.replaceChildren(); requestOverlayEl.classList.toggle("has-items", items.length > 0); for (const item of items) { const li = document.createElement("li"); li.className = "request-item"; const titleEl = document.createElement("span"); titleEl.className = "request-title"; titleEl.textContent = `!bsr ${item.key}`; li.appendChild(titleEl); const who = requesterLine(item); if (who) { const meta = document.createElement("span"); meta.className = "request-meta"; meta.textContent = who; li.appendChild(meta); } requestListEl.appendChild(li); void enrichRequestTitle(item.key, titleEl); } } async function loadRequestQueue() { try { const data = await loadRequestPayload(); requestEmptyEl.textContent = "No pending requests"; requestOverlayEl.classList.remove("request-load-failed"); const raw = useRequestHistorySim() ? data.history ?? [] : data.queue ?? []; const items = raw.slice(0, MAX_REQUESTS); renderRequestList(items); } catch { requestEmptyEl.textContent = "whupsy, database file missing"; requestOverlayEl.classList.add("request-load-failed"); renderRequestList([]); } } void loadRequestQueue(); window.setInterval(() => void loadRequestQueue(), REQUEST_POLL_MS);