diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..69c06e3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Set default behavior to automatically normalize line endings +* text=auto + +# Force bash/shell scripts to always use LF +*.sh text eol=lf +*.bash text eol=lf + +# Force Windows scripts to use CRLF +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore index de9ad49..2ade619 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ fonts/rajdhani-fontfacekit/ ChatRequest.json -chat-request-database.path \ No newline at end of file +overlay.toml \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index b5ec664..69e7f51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,6 @@ # AGENTS.md -OBS/browser overlay that reads Beat Saber Plus Song Overlay events over WebSocket (`ws://localhost:2947/socket`). Serve the folder with **`deno task serve`** (see [`src/server/serve.ts`](src/server/serve.ts)) so the request-queue JSON loads from the same origin; configure the BS+ database path via `CHAT_REQUEST_DATABASE` or `chat-request-database.path`. - -## Preference: HTTP only, no `file://` - -Do **not** add or maintain code paths for opening the overlay as **`file://`**. The client assumes **`http:` / `https:`** for fetching JSON (cache-busted `fetch`). Do not reintroduce XHR/`file:` fallbacks or docs that suggest local file URLs—one supported way: serve with Deno (or another HTTP server) per [`README.md`](README.md). +OBS/browser overlay that reads Beat Saber Plus Song Overlay events over WebSocket (`ws://localhost:2947/socket`). Serve the folder with **`deno task serve`** (see [`src/server/serve.ts`](src/server/serve.ts)) so the request-queue JSON loads from the same origin; configure **`overlay.toml`** (BS+ `Database.json` path, BeatLeader id, and optional UI defaults). ## Files of interest @@ -13,7 +9,8 @@ Do **not** add or maintain code paths for opening the overlay as **`file://`**. - [`src/client/types.ts`](src/client/types.ts) — Shared TypeScript types for BS+ payloads/events and chat request JSON. - [`index.js`](index.js) — Bundled browser output generated from `src/client/index.ts` via `deno task build`. - [`index.css`](index.css) — Layout, theming, visibility toggles driven by body classes and CSS variables from settings. -- [`src/server/serve.ts`](src/server/serve.ts) — Deno static server + optional mapping of `ChatRequest.json` / `database.json` to the real BS+ `Database.json` path. +- [`src/server/serve.ts`](src/server/serve.ts) — Deno static server; reads **`overlay.toml`** and maps `ChatRequest.json` to BS+ `Database.json`; serves **`/api/overlay-config`** (`defaults` JSON for the overlay UI and BeatLeader id). +- [`src/client/overlay-config.ts`](src/client/overlay-config.ts) — TOML field mapping and merging **`/api/overlay-config`** into page defaults. - [`deno.json`](deno.json) — Deno tasks and TypeScript compiler options (`build`, `serve`, `dev`). - [`images/`](images/) — Cover fallback (`unknown.svg`), characteristic icons under `images/characteristic/` (filenames match BS+ characteristic strings). - [`README.md`](README.md) — User-facing usage (Deno, OBS URL, BS+ module). @@ -24,16 +21,10 @@ Do **not** add or maintain code paths for opening the overlay as **`file://`**. Beat Saber Plus itself (game mod) exposes the socket; this repo is only the HTML/CSS/TS client. -## Recent implementation notes (2026-04) +## Implementation notes -- **Live API calls must be runtime-proxied**: browser/OBS hits CORS on BeatLeader/BeatSaver. Keep live requests same-origin via `src/server/serve.ts`: - - `/api/beatleader?path=/...` - - `/api/beatsaver?path=/...` +- **Live API calls are proxied** same-origin via `src/server/serve.ts`: `/api/beatleader?path=/...`, `/api/beatsaver?path=/...` (CORS). - **Map correlation is hash-based**: resolve BeatSaver map/hash first, then BeatLeader leaderboards via `/leaderboards/hash/{hash}`. -- **Score source for friend matching**: use `/leaderboard/{leaderboardId}` scores for stable `playerId` fields; do not rely on v5 score payload shape for player IDs. -- **Mutual friends definition**: intersection of `/player/{id}/followers?type=Following` and `type=Followers` (paged by `page` + `count` only). -- **Overlay feature added**: `#friendScores` panel shows best accuracy per mutual friend for current map, sorted DESC. -- **Debug defaults**: - - mock map key: `4f4e4` - - debug BeatLeader player id: `76561199407393962` - - both debug inputs must have no effect when debug is disabled. +- **Friend scores** use `/leaderboard/{leaderboardId}` for stable `playerId` fields. +- **Mutual friends**: intersection of `/player/{id}/followers?type=Following` and `type=Followers` (paged by `page` + `count`). +- **`#friendScores`**: best accuracy per friend for the current map, sorted descending. diff --git a/README.md b/README.md index 9a4bd37..91f74fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Beat Saber Overlay -Simple Beat Saber stream overlay for [twitch.tv/iza_k](https://www.twitch.tv/iza_k) -Requires [BeatSaberPlus](https://github.com/hardcpp/BeatSaberPlus) +Beat Saber stream overlay, originally based on [twitch.tv/iza_k](https://github.com/ibillingsley/BeatSaber-Overlay) but rewritten in Deno TypeScript. Requires [BeatSaberPlus](https://github.com/hardcpp/BeatSaberPlus) ### Preview @@ -9,26 +8,24 @@ Requires [BeatSaberPlus](https://github.com/hardcpp/BeatSaberPlus) ## Setup -Install [Deno](https://docs.deno.com/runtime/getting_started/installation/) and serve the overlay over HTTP (see below). +* Clone the repo +* Configure `overlay.toml` +* Install [Deno](https://docs.deno.com/runtime/getting_started/installation/) (`winget install Denoland.Deno`) +* Maybe run `deno task build` +* Run `deno task serve` +* In OBS add a Browser source and set the url to the served address (`http://127.0.0.1:8080/index.html`) -The browser overlay source is now TypeScript (`src/client/index.ts`) and is bundled to `index.js` with `deno task build` (run automatically by `deno task serve`). +## Configuration -The server must know where Beat Saber Plus stores **`ChatRequest/Database.json`**. It then serves that file as `ChatRequest.json` and `database.json` (same data) over HTTP—no symlink. +Configuration is `overlay.toml` in the repo root (copy from `overlay.toml.example`; the real file is gitignored). It can set: -**Easiest:** copy `chat-request-database.path.example` to **`chat-request-database.path`** in this repo and put **one line**: the absolute path to `Database.json` (gitignored, so it stays on your machine). +- `chat_request_database` — absolute path to Beat Saber Plus `ChatRequest/Database.json`. The server serves that file as `ChatRequest.json` over HTTP (no symlink). +- `beatleader_player_id` — default BeatLeader id for friend scores (the server logs a warning if this is missing). +- Overlay UI defaults — optional toggles and layout matching the settings dialog: `cover`, `map_info`, `time`, `score`, `friends`, `friend_mode` (`mutual` \| `following` \| `followers`), `bsr`, `right`, `bottom`, `scale`, `fade`. -**Or** set the environment variable (overrides the path file): +Mutual friend mode shows scores from players that you follow and that follow you back on Beatleader. Or in other words, users that are in both your following and follower list. -```powershell -$env:CHAT_REQUEST_DATABASE = "C:\Users\pleb\BSManager\BSInstances\1.40.8\UserData\BeatSaberPlus\ChatRequest\Database.json" -deno task serve -``` - -Then open **`http://127.0.0.1:8080/index.html`** (use the same host and port the terminal prints). Set `PORT` if needed. In OBS, use that URL for the browser source. - -If neither the path file nor `CHAT_REQUEST_DATABASE` is set, the overlay only finds a queue if you place a `ChatRequest.json` copy in the repo folder. - -### Usage +## Usage Clone the repo and run `deno task serve` as above. @@ -37,3 +34,7 @@ Clone the repo and run `deno task serve` as above. - `deno task build` - bundle/check `src/client/index.ts` for browser output (`index.js`) - `deno task serve` - build, then serve `index.html` and JSON files - `deno task dev` - watch and rebuild `index.js` when client TS changes + +## Testing in a browser + +Click anywhere on the page (outside the settings dialog) to toggle preview mode. The song overlay appears with the built-in placeholder labels so you can check layout, toggles, scale, and fade without a game connection. diff --git a/chat-request-database.path b/chat-request-database.path new file mode 100644 index 0000000..fe32244 --- /dev/null +++ b/chat-request-database.path @@ -0,0 +1 @@ +C:\Users\pleb\BSManager\BSInstances\1.40.8\UserData\BeatSaberPlus\ChatRequest\Database.json diff --git a/chat-request-database.path.example b/chat-request-database.path.example deleted file mode 100644 index d2e67ab..0000000 --- a/chat-request-database.path.example +++ /dev/null @@ -1 +0,0 @@ -C:\path\to\UserData\BeatSaberPlus\ChatRequest\Database.json diff --git a/deno.lock b/deno.lock index 24a2248..e402ec4 100644 --- a/deno.lock +++ b/deno.lock @@ -3,6 +3,7 @@ "specifiers": { "jsr:@std/assert@*": "1.0.19", "jsr:@std/cli@^1.0.28": "1.0.28", + "jsr:@std/collections@^1.1.3": "1.1.6", "jsr:@std/encoding@^1.0.10": "1.0.10", "jsr:@std/fmt@^1.0.9": "1.0.9", "jsr:@std/fs@^1.0.23": "1.0.23", @@ -13,7 +14,8 @@ "jsr:@std/net@^1.0.6": "1.0.6", "jsr:@std/path@*": "1.1.4", "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/streams@^1.0.17": "1.0.17" + "jsr:@std/streams@^1.0.17": "1.0.17", + "jsr:@std/toml@*": "1.0.11" }, "jsr": { "@std/assert@1.0.19": { @@ -25,6 +27,9 @@ "@std/cli@1.0.28": { "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a" }, + "@std/collections@1.1.6": { + "integrity": "b458160ce65ea5ad35da05d0a5cbee4b583677c8b443a10d7beb0c4ac63f2baa" + }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" }, @@ -68,6 +73,12 @@ }, "@std/streams@1.0.17": { "integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" + }, + "@std/toml@1.0.11": { + "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", + "dependencies": [ + "jsr:@std/collections" + ] } } } diff --git a/docs/ADR.md b/docs/ADR.md deleted file mode 100644 index f200615..0000000 --- a/docs/ADR.md +++ /dev/null @@ -1,16 +0,0 @@ -# Architectural Decision Record - -## Static web app with typed JavaScript - -The overlay is plain HTML/CSS/JS with no bundler: run it locally with **`deno task serve`** or host the files on any static server. OBS uses an **http(s) URL** to the page. - -Consequences: Ease of use - -- Adding or updating the overlay is **copy files or point OBS at a URL**; nothing to build before use beyond optional Deno for local serving and the chat-request database path. -- Type safety is **optional IDE assistance**, not enforced at publish time (there is no `tsc` in CI by default). - -This dependency-free client shape stays easy to wire as an OBS Browser Source while keeping the codebase maintainable through JSDoc and `types.d.ts`. - -## BeatSaberPlus - -Because I already use it. diff --git a/docs/testing.md b/docs/testing.md deleted file mode 100644 index e1a35be..0000000 --- a/docs/testing.md +++ /dev/null @@ -1,37 +0,0 @@ -# Testing in a browser - -Run **`deno task serve`** from the repo (see [README](../README.md)), then open the overlay in Chromium (Chrome, Edge) or Firefox. - -## Open the page - -Use the URL the server prints, for example: - -`http://127.0.0.1:8080/index.html?scale=1.5` - -Settings live in the **URL fragment** (after `#`). Put query parameters **before** the hash if you use both: `index.html?debug=1#…` - -## Preview the song overlay (no Beat Saber) - -**Click anywhere** on the page (outside the settings dialog) to toggle **preview** mode. The song overlay appears with the built-in placeholder labels so you can check layout, toggles, scale, and fade without a game connection. - -## Request list simulation - -Enable **Debug** in the settings dialog (or add **`?debug=1`** to the URL). The song requests list then uses the **`history`** array from the JSON instead of **`queue`**, so you can see how entries look with the same shape as real data (`key`, `rqn`, `npr`, etc.). The header title is unchanged. - -The Deno server exposes Beat Saber Plus data as **`ChatRequest.json`** and **`database.json`** (same file). With debug, the page tries **`ChatRequest.json`** first, then **`database.json`**. To load a different filename, add **`?requests=yourfile.json`**. - -## What you should see - -- **Developer tools → Console:** log lines such as `Connecting to ws://localhost:2947/socket` on load. If [Beat Saber Plus](https://github.com/hardcpp/BeatSaberPlus) is **not** running with the Song Overlay module listening on that port, the socket will close and the script **retries every 10 seconds**—that is expected. -- **With Beat Saber running** and BS+ Song Overlay enabled: you should see `Connection open.` when the WebSocket succeeds, and map info, time, and score update while you play. - -## Quick UI checks without the game - -- **Click** the page (outside the settings dialog) to toggle **preview** and open the settings strip. -- Change checkboxes and values in the dialog; the **URL hash** should update and layout should reflect toggles and scale. - -## Live data path - -End-to-end testing needs the same pieces as streaming: **Beat Saber**, **Beat Saber Plus** with the **Song Overlay** module active, so `ws://localhost:2947/socket` accepts connections. The browser page only connects to that local WebSocket; it does not start the game server. - -For custom maps, the overlay may request **BeatSaver** over HTTPS; use **Network** in devtools if those requests fail (offline, blocked, or API errors). diff --git a/index.css b/index.css index c83549b..8bb1e15 100644 --- a/index.css +++ b/index.css @@ -47,46 +47,6 @@ body { flex-direction: column; } -/* Debug HUD (body.debug): below main overlay, still visible during body.loading */ -#debugHud { - display: none; - position: absolute; - left: 0; - bottom: 0; - z-index: 6; - max-width: min(100%, 48rem); - padding: 0.35rem 0.55rem; - font-size: 1.15rem; - font-weight: 600; - line-height: 1.3; - white-space: normal; - background: rgba(0, 0, 0, 0.62); - border-radius: 0.35rem; - pointer-events: none; -} - -body.debug #debugHud { - display: block; -} - -#debugHud .debugHud-title { - font-size: 1.25rem; - font-weight: 700; - margin-bottom: 0.15rem; - opacity: 0.95; -} - -#debugHud .debugHud-k { - opacity: 0.72; - margin-right: 0.25rem; -} - -#debugHud code { - font-family: ui-monospace, "Cascadia Code", monospace; - font-size: 0.92em; - word-break: break-all; -} - #songOverlay { flex: 1; display: flex; @@ -452,7 +412,3 @@ body.bottom #time { float: right; margin-left: 1em; } - -body:not(.debug) #mockBsrSetting { - display: none; -} diff --git a/index.html b/index.html index 1deb0ca..e33473b 100644 --- a/index.html +++ b/index.html @@ -44,20 +44,6 @@
————————