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.
6.2 KiB
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 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:
- Host mode — the host (the site owner) has a real server-side Spotify
connection (same shape as
goonk's existingspotify.mjs: Authorization Code flow, refresh token persisted server-side, no per-visitor login). Host picks from their own playlists (playlist-read-private+playlist-read-collaborativescopes) as the song pool for a session. - 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)
- Host-curated mode — described above. Solves the real friction.
- 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." - 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 asgoonk'snow-playingroute) 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. - 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) fromgoonk/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 asgoonk'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.