All checks were successful
Docker / build-and-push (push) Successful in 1m18s
Based on bingo-bango by f8al (MIT) — same core card-generation idea
("both facets": a square is either an exact song title or an artist,
and an artist square lights up on any of their tracks), reworked with
a real host/guest split, live multiplayer sessions, and cover-art
squares. f8al credited in the footer, README, and PROPOSAL.md.
Three ways to play: host a live session (server-side Spotify
connection, join code + QR, caller screen, auto-call from real
playback), join someone else's session (no login needed), or solo
(guest PKCE login or a pasted public playlist URL, one-off card).
Genuinely verified against a real Spotify account throughout, not
just built and assumed working — including two real bugs found and
fixed along the way (Spotify's playlist-tracks endpoint quietly
renamed to /items with a reshaped response; reading playlist tracks
needs real user auth even for public playlists, so the "no guest
login" public-playlist path routes through the host's connection
instead of a dead-end app-only token). Both Docker images built and
run together on a real network with the nginx proxy verified working
end-to-end, and auto-call confirmed detecting an actual track playing
live through Spotify Connect within one poll tick.
118 lines
6.2 KiB
Markdown
118 lines
6.2 KiB
Markdown
# Proposal: `fullhouse` — music bingo, live-session and self-service
|
|
|
|
**Status:** proposed, build in progress. Self-contained — written so a fresh
|
|
agent with no prior conversation context can pick this up and implement it
|
|
without anything explained first.
|
|
|
|
## Origin & attribution
|
|
|
|
Inspired by [bingo-bango](https://github.com/f8al/bingo-bango) by **f8al** —
|
|
a fully client-side (Spotify PKCE, no backend) music bingo card generator.
|
|
`fullhouse` keeps the core idea (Spotify playlist → bingo cards, "both
|
|
facets" title+artist marking) and the card-generation algorithm's shape, but
|
|
changes the architecture significantly enough that it's a fresh build, not a
|
|
fork: a real server-side host mode, live multiplayer sessions, cover-art
|
|
squares, and heavier use of the Spotify API. MIT license from the original
|
|
requires the copyright notice + license text be retained wherever the
|
|
original code/algorithm is directly reused — credit f8al clearly in the
|
|
README and footer regardless of how much code ends up shared verbatim.
|
|
|
|
## Motivation
|
|
|
|
The original requires *every player* to individually log into Spotify to
|
|
pick a playlist — real friction at an actual game night where guests don't
|
|
want to OAuth their own account just to get a bingo card. The fix: a proper
|
|
host/guest split.
|
|
|
|
## Modes
|
|
|
|
Both exist simultaneously, chosen per session, not one replacing the other:
|
|
|
|
1. **Host mode** — the host (the site owner) has a real server-side Spotify
|
|
connection (same shape as `goonk`'s existing `spotify.mjs`: Authorization
|
|
Code flow, refresh token persisted server-side, no per-visitor login).
|
|
Host picks from *their own* playlists (`playlist-read-private` +
|
|
`playlist-read-collaborative` scopes) as the song pool for a session.
|
|
2. **Guest self-service mode** — the original flow, kept: a visitor with no
|
|
relationship to the host logs in via their own Spotify PKCE flow and
|
|
generates a card from their own playlist, standalone, no session/host
|
|
involved. Also supports pasting a public playlist URL with **no login at
|
|
all**, via an app-level Client Credentials token (public playlists don't
|
|
need user auth to read) — simpler than PKCE for the common "just grab a
|
|
public playlist" case, and something the original doesn't offer.
|
|
|
|
## The four differentiators (all in scope for this build)
|
|
|
|
1. **Host-curated mode** — described above. Solves the real friction.
|
|
2. **Live session mode** — host starts a session, gets a join code + QR.
|
|
Guests join via the code (no login required in a host-curated session —
|
|
the host already authorized the song pool), each gets an independently
|
|
seeded card. A "caller" screen shows the current track large, for the
|
|
host to read aloud or leave visible on a shared screen. State sync is
|
|
**polling**, not WebSockets — deliberately, matching this whole family's
|
|
precedent (`keep watch`, `wisp`'s sweep): a session isn't latency-
|
|
sensitive enough to justify the operational complexity of a socket
|
|
server. Poll every 3-5s for "what's been called."
|
|
3. **Live now-playing auto-call** — if the host is actually playing the
|
|
session's pool through Spotify Connect, poll `/me/player/currently-
|
|
playing` (same pattern as `goonk`'s `now-playing` route) and
|
|
auto-mark a track "called" the moment it starts, matched against the
|
|
pool by track ID. Falls back to a manual "call next" button (classic
|
|
random-draw-without-replacement bingo caller) for hosts not using live
|
|
Spotify Connect playback during the game — auto-call is a bonus layer
|
|
over manual, not a replacement for it.
|
|
4. **Cover-art squares** — squares can show album art (from the same
|
|
playlist-track API response, `track.album.images`, already fetched for
|
|
everything else) instead of, or alongside, text. A visual variant beyond
|
|
what the original offers.
|
|
|
|
## Architecture
|
|
|
|
**Server** (small Express app, same shape as `goonk`'s `api/`):
|
|
- Host OAuth: `/auth/login`, `/auth/callback`, refresh-token persistence —
|
|
ported near-verbatim in pattern (not code) from `goonk/api/routes/
|
|
spotify.mjs`, since that flow is already proven correct in this family.
|
|
- `GET /api/host/playlists` — the host's own playlists, cached.
|
|
- `GET /api/host/playlist/:id/tracks` — full track+artist+album-art data
|
|
for a chosen playlist, cached.
|
|
- `GET /api/public-playlist?url=` — Client Credentials app token, fetch a
|
|
*public* playlist's tracks with no user login. Client Credentials tokens
|
|
are cached in memory and refreshed on expiry, same shape as the existing
|
|
user-token cache pattern.
|
|
- Session state: `POST /api/session` (host creates one from a chosen
|
|
playlist + mode), `GET /api/session/:code` (guest join — hands back pool
|
|
metadata, NOT the host's Spotify tokens), `POST /api/session/:code/call`
|
|
(host marks a track called, or the auto-call poller does), `GET /api/
|
|
session/:code/state` (guests poll this for the called-track list).
|
|
In-memory session store is fine — sessions are ephemeral (a single game
|
|
night), no need for persistence across restarts.
|
|
- `GET /api/host/now-playing` — thin wrapper the auto-call poller uses,
|
|
same call shape as `goonk`'s existing now-playing route.
|
|
|
|
**Client**: Vite + TypeScript, no framework — matches the `typo`/`latent`
|
|
family's "plain, no heavy build" ethos more than the original's React
|
|
choice. The card-generation engine is a pure, dependency-free TS module
|
|
(same shape as the original's `src/cards/`), extended with a `coverArt`
|
|
render mode alongside the text mode.
|
|
|
|
**Guest PKCE mode** stays fully client-side (no server involvement at all)
|
|
for the self-service path — same as the original, since that's the whole
|
|
point of PKCE (no secret needed, tokens never touch a server the host
|
|
controls).
|
|
|
|
## Theming
|
|
|
|
Dark/mono/green house style shared with `typo`/`flit`/`wisp` (see any of
|
|
those `style.css` files for the exact CSS custom properties), not the
|
|
original's own visual design. Reads as part of this family, not a
|
|
standalone fork.
|
|
|
|
## Non-goals
|
|
|
|
- No accounts beyond the host's own Spotify connection — guests never
|
|
create anything, a join code is the only credential a session needs.
|
|
- No persistent session history — a session lives in memory for its game
|
|
night and is gone after.
|
|
- Not trying to be a general Spotify app — scoped tightly to bingo card
|
|
generation + live session calling.
|