Record the phase 5 ScoreSaber merge and map OST hashes in the sync
ScoreSaber names official levels "ost_<LevelId>" where BeatLeader uses "<levelid>"; ssMapHash applies that for leaderboard snapshots and maps. The plan records the merge results and where it differs from the steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import * as path from 'node:path';
|
||||
import { ARCHIVE_DIR, BL_API, DEFAULT_MAPS_SINCE, LEGACY_PLAYER_ID, MAIN_PLAYER_ID, PLAYER_IDS, TOKENS_FILE, USER_AGENT, loadDotEnv, parseSince } from './config.ts';
|
||||
import { getAccessToken, login, readTokens, refresh } from './auth.ts';
|
||||
import { archivedScores, blHashes, exportLeaderboards, exportProfile, exportReplays, exportScoreStats, exportScores, newestScoreTime } from './beatleader.ts';
|
||||
import { archivedSSScores, exportSSPlayer, exportSSReplays, exportSSScores, newestSSScoreTime, ssHashes } from './scoresaber.ts';
|
||||
import { archivedSSScores, exportSSPlayer, exportSSReplays, exportSSScores, newestSSScoreTime, ssHashes, ssMapHash } from './scoresaber.ts';
|
||||
import { exportMaps } from './beatsaver.ts';
|
||||
import { archivePath, exists, listFiles, readState, updateState } from './store.ts';
|
||||
|
||||
@@ -83,7 +83,7 @@ function playedHashes(since: number): string[] {
|
||||
function mapHashes(mapsSince: number): string[] {
|
||||
const legacyWithReplay = archivedSSScores(LEGACY_PLAYER_ID, 0)
|
||||
.filter((it) => exists(archivePath('scoresaber', LEGACY_PLAYER_ID, 'replays', `${it.score.id}.dat`)))
|
||||
.map((it) => it.leaderboard.songHash.toLowerCase());
|
||||
.map(ssMapHash);
|
||||
return [...new Set([...playedHashes(mapsSince), ...legacyWithReplay])].sort();
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ async function sync(opts: { ratings: boolean; ssReplays: boolean }): Promise<num
|
||||
await exportSSPlayer(MAIN_PLAYER_ID);
|
||||
const ssSince = Math.max(0, newestSSScoreTime(MAIN_PLAYER_ID) - SYNC_OVERLAP_S);
|
||||
const newSS = await exportSSScores(MAIN_PLAYER_ID, ssSince);
|
||||
const newSSHashes = [...new Set(newSS.map((it) => it.leaderboard.songHash.toLowerCase()))].sort();
|
||||
const newSSHashes = [...new Set(newSS.map(ssMapHash))].sort();
|
||||
await exportLeaderboards(newSSHashes.filter((h) => !newBLHashes.includes(h)));
|
||||
if (opts.ssReplays) await exportSSReplays(MAIN_PLAYER_ID, ssSince);
|
||||
|
||||
|
||||
@@ -102,6 +102,14 @@ function seedFromScraper(playerId: string, items: SSItem[]): void {
|
||||
if (copied) console.log(` copied ${copied} replays from ${dir}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* The map hash BeatLeader and the archive use for a ScoreSaber item: lowercase, and for official
|
||||
* levels the level id without ScoreSaber's "ost_" prefix (BeatLeader: "beatsaber", SS: "ost_BeatSaber").
|
||||
*/
|
||||
export function ssMapHash(it: SSItem): string {
|
||||
return it.leaderboard.songHash.toLowerCase().replace(/^ost_/, '');
|
||||
}
|
||||
|
||||
export function ssHashes(playerId: string, sinceUnix = 0): string[] {
|
||||
return [...new Set(archivedSSScores(playerId, sinceUnix).map((it) => it.leaderboard.songHash.toLowerCase()))].sort();
|
||||
return [...new Set(archivedSSScores(playerId, sinceUnix).map(ssMapHash))].sort();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user