Fix scores from friends
This commit is contained in:
parent
d73c1ac495
commit
f2181c7cf3
BIN
assets/notlikesteve.webp
Normal file
BIN
assets/notlikesteve.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 744 B |
BIN
assets/peepohigh.webp
Normal file
BIN
assets/peepohigh.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@ -307,6 +307,15 @@ span:empty {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
opacity: 0.92;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
#friendScoresHeaderImg {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#friendScoresList {
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="friendScores" aria-live="polite">
|
||||
<div id="friendScoresHeader">Mutual friends on BeatLeader</div>
|
||||
<div id="friendScoresHeader"><span id="friendScoresHeaderText">frenz!</span> <img id="friendScoresHeaderImg" src="assets/peepohigh.webp" alt=""></div>
|
||||
<ol id="friendScoresList"></ol>
|
||||
<div id="friendScoresEmpty">No map loaded</div>
|
||||
</div>
|
||||
@ -66,6 +66,11 @@
|
||||
<label>Show time: <input id="timeInput" type="checkbox"></label>
|
||||
<label>Show score: <input id="scoreInput" type="checkbox"></label>
|
||||
<label>Show friend scores: <input id="friendsInput" type="checkbox"></label>
|
||||
<label>Friend list mode: <select id="friendModeInput">
|
||||
<option value="mutual">Followed + follower (mutual)</option>
|
||||
<option value="following">Following (I follow them)</option>
|
||||
<option value="followers">Followers (they follow me)</option>
|
||||
</select></label>
|
||||
<label>Show BSR / map id: <input id="bsrInput" type="checkbox"></label>
|
||||
<label>Position: <select id="positionInput">
|
||||
<option value="[false,false]">Top left</option>
|
||||
|
||||
39
index.js
39
index.js
@ -118,16 +118,18 @@ async function fetchAllFollowers(playerId, type2, maxPages = 100) {
|
||||
}
|
||||
return all;
|
||||
}
|
||||
async function fetchMutualFriendIds(playerId, maxPages = 100) {
|
||||
async function fetchFriendIds(playerId, mode, maxPages = 100) {
|
||||
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 = /* @__PURE__ */ new Set();
|
||||
for (const entry of followers) {
|
||||
const id = String(entry.id);
|
||||
for (const id of followerIds) {
|
||||
if (followingIds.has(id)) {
|
||||
mutuals.add(id);
|
||||
}
|
||||
@ -277,6 +279,8 @@ var mistakes = must("mistakes");
|
||||
var friendScoresPanel = must("friendScores");
|
||||
var friendScoresList = must("friendScoresList");
|
||||
var friendScoresEmpty = must("friendScoresEmpty");
|
||||
var friendScoresHeaderText = must("friendScoresHeaderText");
|
||||
var friendScoresHeaderImg = must("friendScoresHeaderImg");
|
||||
var currentMapHash = "";
|
||||
var friendScoreRequestId = 0;
|
||||
function updateScore(score) {
|
||||
@ -288,13 +292,17 @@ function updateScore(score) {
|
||||
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 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";
|
||||
@ -329,7 +337,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) {
|
||||
@ -337,7 +345,8 @@ 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);
|
||||
@ -371,6 +380,7 @@ var settings = {
|
||||
time: true,
|
||||
score: true,
|
||||
friends: true,
|
||||
friendMode: "mutual",
|
||||
bsr: false,
|
||||
debug: false,
|
||||
mockBsr: "4f4e4",
|
||||
@ -451,6 +461,13 @@ for (const key of [
|
||||
}
|
||||
};
|
||||
}
|
||||
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 = () => {
|
||||
@ -495,6 +512,7 @@ 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";
|
||||
}
|
||||
@ -544,19 +562,26 @@ function requesterLine(item) {
|
||||
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) 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 {
|
||||
requestTitleMisses.add(key);
|
||||
}
|
||||
}
|
||||
function renderRequestList(items) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user